Theme dev localhost session stops persisting on password-protected store when authenticated with Theme Access

I have reproduced the issue on the latest CLI version.

Yes, I am on the latest version

I have searched existing posts and this report is not a duplicate.

Yes, this isn’t a duplicate

In which of these areas are you experiencing a problem?

Theme

Expected behavior

With --store-password supplied, shopify theme dev should keep the local preview (127.0.0.1:9292) signed in to the password-protected storefront for the whole session, so every page requested through the local proxy renders, the same way the first page does and the same way the Shopify-hosted preview link does.

Actual behavior

When the CLI is authenticated with a Theme Access token, the local proxy initially serves one or more pages successfully (one page in most runs; four pages over 28 seconds in the verbose run with a freshly created token), but the password-protected storefront session does not persist reliably. A subsequent request is eventually redirected to /password. The /password page renders (GET 200), but the correct storefront password submitted through the proxy is refused (POST 401; Chrome shows ERR_INVALID_RESPONSE). The same behaviour occurs on CLI 4.7.0 and 4.8.0.

The same password is accepted on the Shopify-hosted preview. Theme syncing (uploads from the watched folder) works throughout. Shopify Developer Support checked the store and confirmed there are no restrictions on it.

When the CLI is instead authenticated with shopify auth login (a Shopify account), the identical command works: a manual browser session made twenty page requests in a row, all 200, and the password page never appeared. So the failure reproduces when theme dev is authenticated using Theme Access, and did not reproduce in the control run using Shopify-account authentication on the same machine and store.

Note on rate limiting: earlier the same day, an automated check retried the password submission through the proxy several times in quick succession and started receiving 429 responses. Those were the storefront rate-limiting repeated failed submissions and are not part of this report. No automated retry loop was running during any of the reproductions below. Each POST /password shown was a deliberate single manual submission.

Reproduction steps

  1. A password-protected development store, with storefront password protection enabled.
  2. Create a Theme Access password for that store and supply it in SHOPIFY_CLI_THEME_TOKEN.
  3. Run, from the theme folder: shopify theme dev --store= --theme --store-password --path
  4. Open http://127.0.0.1:9292 in a browser and navigate or refresh several times. One or more initial requests render normally.
  5. Eventually a subsequent request is redirected to /password.
  6. Enter the correct storefront password once. The POST returns 401.

Control: repeat steps 3 to 5 after shopify auth login with a Shopify account that has the Themes permission, with SHOPIFY_CLI_THEME_TOKEN unset. Every page renders and /password never appears.

Request lines as printed by the CLI itself (times local):

CLI 4.8.0, Theme Access token, one manual browser session, password entered once each time asked
08:49:23 GET 200 /
08:49:31 GET 302 /
08:49:31 GET 200 /password
08:49:31 GET 404 /favicon.ico
08:49:35 POST 401 /password
08:50:07 GET 302 /collections/floor-lights
08:50:08 GET 200 /password
08:50:13 POST 401 /password

CLI 4.7.0 (run through “npx --yes @shopify/cli@4.7.0”, global 4.8.0 untouched), Theme Access token, one manual browser session, password entered once
09:01:07 GET 200 /
09:01:09 GET 302 /
09:01:09 GET 200 /password
09:01:10 GET 404 /favicon.ico
09:01:14 POST 401 /password
09:01:31 GET 302 /
09:01:32 GET 200 /password

CLI 4.8.0, authenticated with shopify auth login, otherwise identical, one manual browser session
09:57:31 GET 200 /
09:57:33 GET 200 /collections/floor-lights
09:57:38 GET 200 /collections/floor-lights
09:57:40 GET 200 /
09:57:49 GET 200 /collections/floor-lights
09:57:51 GET 200 /products/
09:57:52 GET 200 /apps/
09:58:10 GET 200 /collections/floor-lights
09:58:12 GET 200 /products/
09:58:16 GET 200 /products/
09:59:14 GET 200 /products/?variant=
09:59:16 GET 200 /
09:59:17 GET 200 /collections/floor-lights

Also checked: 127.0.0.1:9292 is listening (bound to 127.0.0.1) and answers throughout; localhost resolves to ::1 first on this machine, so the numeric address was used. The “Request »” lines only show requests passing through the local proxy; the verbose output below confirms that the CLI itself submits the storefront password during dev-session setup, before the local server begins serving browser requests.

Verbose output

Captured 22 Sep 2026 with --verbose, CLI 4.8.0, a freshly created Theme Access token, one manual browser session (home page, refresh, a collection, home page, the collection again, then the password entered once). The theme-file upload lines from startup are cut for length; nothing else is edited. Request IDs are left in so the team can trace them. Timestamps in the verbose lines are UTC; the “Request »” lines are local time (UTC+1).

2026-09-22T10:29:51.862Z: Running command theme dev
2026-09-22T10:29:51.904Z: Checking if <theme folder> is a directory...
2026-09-22T10:29:51.920Z: Ensuring that the user is authenticated with the Theme API with the following scopes:
[]
2026-09-22T10:29:51.924Z: Sending "Admin" GraphQL request:
  query publicApiVersions { ... }
With request headers:
 - X-Shopify-Shop: <development store>
 - User-Agent: Shopify CLI; v=4.8.0
to https://theme-kit-access.shopifyapps.com/cli/admin/api/unstable/graphql.json
2026-09-22T10:29:52.316Z: Request to https://theme-kit-access.shopifyapps.com/cli/admin/api/unstable/graphql.json completed in 392 ms
 - x-request-id: 1c72de72-43ce-468a-805b-5628b37dcc37-1790072992
2026-09-22T10:29:52.317Z: Sending "Admin" GraphQL request:
  query getThemes($after: String) { ... }
2026-09-22T10:29:52.991Z: Could not obtain crawler signature headers; continuing without them. Field 'storefrontCrawlerSignatures' doesn't accept argument 'cli'
Variable $cli is declared by StorefrontCrawlerSignatures but not used
Request ID: 69a7fd5b-6325-4cd7-a1d2-3024ed18073c-1790072992
2026-09-22T10:29:52.993Z: Sending POST request to URL https://<development store>/password
With request headers:
 - cache-control: no-cache
 - content-type: application/x-www-form-urlencoded
2026-09-22T10:29:53.124Z: Request to https://<development store>/password completed in 131 ms
With response headers:
 - cache-control: private, no-store
 - content-type: text/html; charset=utf-8
 - server-timing: processing;dur=49;desc="gc:1", db;dur=18, db_async;dur=4.842, pageType;desc="password"
 - x-request-id: 36b273b9-bc1f-4ca1-a56e-da7a9de4aa88-1790072993
2026-09-22T10:29:53.125Z: Setting storefront password for shop <development store>...
2026-09-22T10:29:53.139Z: Port 9292 is free
2026-09-22T10:29:53.139Z: Sending "Admin" GraphQL request:
  query StorefrontCrawlerSignatures(...) { ... }
[ ... theme file reads and uploads to the development theme, all reported as success ... ]
2026-09-22T10:29:53.813Z: Sending POST request to URL https://theme-kit-access.shopifyapps.com/cli/sfr/password
With request headers:
 - X-Shopify-Shop: <development store>
 - User-Agent: Shopify CLI; v=4.8.0
2026-09-22T10:29:54.343Z: Request to https://theme-kit-access.shopifyapps.com/cli/sfr/password completed in 531 ms
With response headers:
 - cache-control: no-cache
 - content-type: application/json; charset=utf-8
 - server-timing: processing;dur=361, socket_queue;dur=3.576, util;dur=5.0
 - x-request-id: f4c2fab7-9f14-479e-9ce9-b890e6f7b311-1790072993
[ ... remaining startup uploads ... ]
2026-09-22T10:30:01.643Z: ← 200 (request_id: 785680e5-dc6e-4278-89a8-7a45edd0e5ae-1790072997)
• 11:30:01 Request »    GET 200 /
2026-09-22T10:30:02.367Z: Files to be deleted:
2026-09-22T10:30:17.283Z: → Rendering https://theme-kit-access.shopifyapps.com/cli/sfr/?_fd=0&pb=0...
2026-09-22T10:30:18.022Z: ← 200 (request_id: 346c7d7b-4ec2-4175-b4d0-ee1e761feec4-1790073017)
• 11:30:18 Request »    GET 200 /
2026-09-22T10:30:19.949Z: → Rendering https://theme-kit-access.shopifyapps.com/cli/sfr/collections/floor-lights?_fd=0&pb=0...
2026-09-22T10:30:20.775Z: ← 200 (request_id: 3c8a5598-fcd2-4c04-a93a-fe686beb2815-1790073020)
• 11:30:20 Request »    GET 200 /collections/floor-lights
2026-09-22T10:30:27.087Z: → Rendering https://theme-kit-access.shopifyapps.com/cli/sfr/?_fd=0&pb=0...
2026-09-22T10:30:27.695Z: ← 200 (request_id: dee10628-fcc6-4b97-a4a1-3d7298e5e40f-1790073027)
• 11:30:27 Request »    GET 200 /
2026-09-22T10:30:29.000Z: → Rendering https://theme-kit-access.shopifyapps.com/cli/sfr/collections/floor-lights?_fd=0&pb=0...
2026-09-22T10:30:29.319Z: ← 302 (request_id: 414a8191-2110-418e-bcc7-00a9c42c9bb9-1790073029)
• 11:30:29 Request »    GET 302 /collections/floor-lights
2026-09-22T10:30:29.324Z: → Rendering https://theme-kit-access.shopifyapps.com/cli/sfr/password?_fd=0&pb=0...
2026-09-22T10:30:29.945Z: ← 200 (request_id: 99d6954e-b0ae-43fa-89c5-4e7e6006c558-1790073029)
• 11:30:29 Request »    GET 200 /password
• 11:30:33 Request »   POST 401 /password

In this verbose run the session held for four page renders over 28 seconds before the fifth was answered 302 by https://theme-kit-access.shopifyapps.com/cli/sfr/…; in the earlier runs it held for one.

No cookie or token values appear in the excerpt below. The store address and theme-file listing have been redacted.

Operating system

Microsoft Windows 10 Pro 10.0.19045

CLI version

4.8.0 (global install). Also reproduced on 4.7.0 run through npx.

Shell

Git Bash (bash 5.2.37, MINGW64). Windows PowerShell 5.1 is also present; the command was launched from Git Bash.

Nodejs version

v24.1.0 (npm 11.3.0)

What language and version are you using in your application?

Not applicable: theme development only, no app. Liquid theme; Node v24.1.0 runs the CLI.

Hey @Josh_Smith - thanks for the detailed report and for testing both authentication methods.

We’re looking into the session persistence issue with Theme Access and the subsequent password submission failure. I’ll update this thread when we have more to share.