Skip to content

Recover theme commands after a preview store is claimed - #8390

Draft
dmerand wants to merge 1 commit into
mainfrom
donald/claimed-preview-theme-recovery-v2
Draft

Recover theme commands after a preview store is claimed#8390
dmerand wants to merge 1 commit into
mainfrom
donald/claimed-preview-theme-recovery-v2

Conversation

@dmerand

@dmerand dmerand commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Supersedes #8190. Builds on #8349, which added the store command recovery after a preview store is claimed.

The CLI gets no claim event when a preview store is claimed. The first signal is an HTTP 401 from the stored preview token. Before this change, theme commands swallowed that 401: fetchTheme treated it as a missing theme, ThemeManager dropped the stored theme ID, and multi-environment runs printed the raw error without recovery steps.

WHAT is this pull request doing?

  • Theme commands classify an HTTP 401 as invalid stored auth only when the cached session is a preview store-auth session. Standard refreshable sessions and explicit --password sessions are never cleared.
  • On that 401, the CLI clears the stored preview session and tells the user to run shopify store auth again. A 404 still means a missing theme.
  • fetchTheme propagates a 401 for all supported error shapes (status, response.status, and statusCode) instead of returning undefined, so ThemeManager keeps the stored theme ID.
  • fetchApiVersions preserves the HTTP status on its connection error (AdminApiRequestError) so callers can classify it.
  • Multi-environment runs keep FatalError next steps in the per-environment failure output and continue with the healthy environments.
  • Moves the shared stored-auth recovery helpers from @shopify/store to @shopify/cli-kit (store-auth-recovery). The helper default policy is 401-only; store commands opt in to [401, 404] explicitly, so store behavior from Fix reauthentication after a preview store is claimed #8349 is unchanged.

How to test your changes?

  1. Run shopify store create preview, then run a theme command (for example shopify theme list --store <preview-store>) with the stored preview session.
  2. Claim the preview store in the browser.
  3. Rerun the theme command: it fails with "The preview store … has likely been claimed" and a shopify store auth next step, instead of a missing-theme error.

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

Assisted-By: devx/6b8a4c45-3042-4ae7-8f77-3e1296bbe6ac
@github-actions github-actions Bot added the Area: @shopify/theme @shopify/theme package issues label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

packages/cli-kit/dist/public/node/store-auth-recovery.d.ts
import type { LocalStorage } from './local-storage.js';
import type { StoreAuthSessionSchema, StoredStoreAppSession } from './store-auth-session.js';
/**
 * Throws an actionable error when no store app authentication is stored.
 *
 * @param store - The store FQDN that needs authentication.
 * @throws AbortError with a `store auth` next step.
 */
export declare function throwMissingStoredStoreAuthError(store: string): never;
/**
 * Throws an actionable error that directs the user to re-authenticate a stored session.
 *
 * @param message - The reason that the stored session cannot be used.
 * @param session - The stored session to authenticate again.
 * @throws AbortError with a `store auth` next step.
 */
export declare function throwReauthenticateStoreAuthError(message: string, session: StoredStoreAppSession): never;
/**
 * Throws the invalid-session error for a stored store app session.
 *
 * @param session - The stored session rejected by Shopify.
 * @throws AbortError with a `store auth` next step.
 */
export declare function throwStoredStoreAuthInvalidError(session: StoredStoreAppSession): never;
interface InvalidStoredStoreAuthOptions {
    invalidStatuses?: ReadonlyArray<number>;
    storage?: LocalStorage<StoreAuthSessionSchema>;
}
/**
 * Clears and reports a rejected stored session when its HTTP status is invalid for the caller.
 *
 * @param error - The rejected API error.
 * @param session - The stored session used for the request.
 * @param options - Statuses to classify and an optional storage override.
 * @throws AbortError with a `store auth` next step when the session is invalid.
 */
export declare function throwIfStoredStoreAuthIsInvalid(error: unknown, session: StoredStoreAppSession, options?: InvalidStoredStoreAuthOptions): void;
export {};

Existing type declarations

packages/cli-kit/dist/public/node/store-auth-session.d.ts
@@ -55,7 +55,8 @@ interface StoredStoreAppSessionBucket {
         [userId: string]: StoredStoreAppSession;
     };
 }
-interface StoreAuthSessionSchema {
+/** Store auth sessions, keyed by the store auth storage key. */
+export interface StoreAuthSessionSchema {
     [key: string]: StoredStoreAppSessionBucket;
 }
 /**
packages/cli-kit/dist/public/node/api/admin.d.ts
@@ -1,8 +1,14 @@
 import { GraphQLResponseOptions, GraphQLVariables } from './graphql.js';
 import { AdminSession } from '../session.js';
+import { AbortError } from '../error.js';
 import { RequestModeInput } from '../http.js';
 import { Variables } from 'graphql-request';
 import { TypedDocumentNode } from '@graphql-typed-document-node/core';
+/** Error that preserves an Admin API status for caller-specific recovery. */
+export declare class AdminApiRequestError extends AbortError {
+    readonly status: number;
+    constructor(status: number, message: string);
+}
 /**
  * Executes a GraphQL query against the Admin API.
  *

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

Labels

Area: @shopify/theme @shopify/theme package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant