Skip to content

Explain when a token can't manage dev stores instead of a raw 401 - #8978

Open
egaodd wants to merge 1 commit into
mainfrom
eddie/store-commands-token-401
Open

egaodd wants to merge 1 commit into
mainfrom
eddie/store-commands-token-401

Conversation

@egaodd

@egaodd egaodd commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Closes shop/issues-develop#24293

TL;DR: With a token whose service account doesn't have Dev store management, store create dev, store list and store delete now stop with "This token can't manage dev stores." and a next step, instead of crashing with a raw GraphQL Error (Code: 401) and a stack trace.

WHY are these changes introduced?

Found in the Organization Tokens end-to-end run on October 16 (finding F3). With an app-only token:

$ shopify store create dev --name "App-only store" --organization-id 3 --plan basic --country US
GraphQL Error (Code: 401): {"response":{"error":"","status":401,"headers":{}},"request":{"query":"mutation CreateAppDevelopmentStore(...
  To investigate the issue, examine this stack trace:

What happens:

  1. The Business Platform token exchange succeeds. The CLI requests no explicit scopes, so Identity issues whatever Business Platform scopes the token has.
  2. The Organizations API refuses a token that has no store-management scope while authorizing the client, before any GraphQL runs. It answers with a 401 and an empty body.
  3. cli-kit rethrows 401s without mapping them, so the refresh handler can run. With an automation token, the refresh exchanges the same token again, and the retry gets the same 401.
  4. Nothing catches the second 401. The raw graphql-request error reaches the top level and is reported as an unexpected error, with a stack trace.

Expired, revoked and invalid tokens never get this far. Identity refuses the exchange, and the CLI already stops with "The custom token provided can't be used for the Business Platform API." A token revoked partway through a command fails the same way on the refresh. So a 401 that survives the refresh means the token lacks Dev store management.

WHAT is this pull request doing?

  • storeManagementRequestDoc() in the organizations package, next to businessPlatformTokenRefreshHandler and exported the same way. It calls businessPlatformOrganizationsRequestDoc unchanged. If the request still fails with a 401 after the refresh retry while an automation token is in use (getAppAutomationToken()), it throws an AbortError:

    This token can't manage dev stores.
    Use a token from a service account with Dev store management. You can create one in your organization's settings in the Shopify admin.

    "Dev store management" is the option's label in the Shopify admin. The status is read from the error's shape, as in store's admin-errors.ts, so the package doesn't import graphql-request.

  • Store commands use it for all six of their Organizations API calls:

    • store create dev: create and poll;
    • store delete: store lookup, delete and poll;
    • store list.
  • Unchanged:

    • Human sessions: a 401 is rethrown as before.
    • The refresh retry: it's what gives a token revoked mid-command the clean exchange error.
    • 403s: this API only sends one for a locked organization ([ORGANIZATION_DISABLED]), so mapping it would give the wrong reason.
    • Other errors, and every other Organizations API caller, including the app commands, which call cli-kit directly.
  • store info and organization list can't produce this crash. store info's Organizations API lookup already ignores errors. Whether store info should explain a missing store for a service-account token belongs with shop/issues-develop#24291. organization list uses the Destinations API, which app tokens can read.

  • No changeset: store commands with automation tokens ship for the first time in 4.10, under the SHOPIFY_SERVICE_ACCOUNT_TOKEN changeset.

How to manually test your changes?

With an App Automation Token, or a service account token without Dev store management:

export SHOPIFY_SERVICE_ACCOUNT_TOKEN=<token>
shopify store list --organization-id <its org id>
shopify store create dev --name "App-only store" --organization-id <its org id> --plan basic --country US
shopify store delete --store <a dev store in that org> --organization-id <its org id> --force

Each one stops with "This token can't manage dev stores." and the next step, with no stack trace. shopify organization list still lists the organization.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

With an automation token that has no Dev store management, store create
dev, store list and store delete crashed with a raw "GraphQL Error (Code:
401)" and a stack trace. The Business Platform token exchange succeeds,
because the CLI requests no explicit scopes, and the Organizations API then
refuses the token with an empty 401. cli-kit's refresh retry exchanges the
same token again, gets the same 401, and the raw graphql-request error
reaches the top level as an unexpected error.

Store commands now send their Organizations API requests through
storeManagementRequestDoc, next to businessPlatformTokenRefreshHandler in
the organizations package. When a 401 survives the refresh retry while an
automation token is in use, it aborts with "This token can't manage dev
stores." and points to creating a service account with Dev store
management. Expired, revoked and invalid tokens still fail earlier, at the
token exchange. A 403, which this API only sends for a locked
organization, human sessions and other errors are unchanged.

Co-authored-by: Eddie Gao <eddie.gao@shopify.com>
@egaodd
egaodd requested a review from a team as a code owner October 21, 2026 17:05
@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label Oct 21, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants