DRAFT — not posted. For Paul's approval before anything goes to Weaverse/pilot.
Repo: Weaverse/pilot
Title: shopify hydrogen dev aborts on startup in a network-restricted environment (@shopify/cli 4.7.1 cancellation race)
Summary
On Pilot 4a99511d (v2026.9.7), npm run dev exits 1 during startup whenever the environment cannot
reach the npm registry. The failure comes from @shopify/cli's startup version-check path, not from
Pilot code or from Vite/Hydrogen:
> @weaverse/pilot@2026.9.7 dev
> react-router typegen && shopify hydrogen dev --codegen --port "$PORT"
…
[vite] (client) Re-optimizing dependencies because vite config has changed
[vite] (ssr) Re-optimizing dependencies because vite config has changed
Error: The `onCancel` handler was attached after the promise settled.
at o (node_modules/@shopify/cli/dist/latest-version-LDWU5F7A.js:11:17603)
at d (node_modules/@shopify/cli/dist/latest-version-LDWU5F7A.js:19:47843)
at t.<anonymous> (node_modules/@shopify/cli/dist/latest-version-LDWU5F7A.js:19:49069)
at t.wrapper (node:events:639:12)
at t.emit (node:events:514:20)
at t.emit (node:domain:473:12)
at node_modules/@shopify/cli/dist/latest-version-LDWU5F7A.js:19:37968
at runNextTicks (node:internal/process/task_queues:65:5)
at processTimers (node:internal/timers:615:9)
The message is p-cancelable's guard: an onCancel handler is attached to a promise that has
already settled. The frame chain ends in processTimers, so the throw is raised from a timer inside
the version-check/self-upgrade flow, escapes through node:domain, and terminates the CLI process
before the dev server ever listens. The dev server never prints ➜ Local:; the surrounding Miniflare
instance then reports ERR_DISPOSED as a consequence of the teardown.
Environment
- Pilot
4a99511d (v2026.9.7), unmodified lockfile (@shopify/cli 4.7.1; registry latest 4.8.0)
- Node 26.8.2, npm 11.19.1, Debian trixie (
node:26-slim), linux/arm64
- Dev command exactly as Pilot defines it, only
--port taken from the environment
- No egress to the npm registry; name resolution and outbound TCP are denied by the host
Reproduction
npm ci on a machine with network, so node_modules is complete.
- Remove registry reachability (any of: firewall DROP, firewall REJECT, or no route at all).
PORT=3458 npm run dev.
Observed: exits 1 with the trace above. The CLI announces
✨ New version of Shopify CLI available! (4.7.1 → 4.8.0) Now upgrading by running: npm install -g @shopify/cli@latest
when it can read the registry, so the crash sits on the path that learns about / installs a newer CLI.
Notably not the cause
- Denial latency. Measured in the failing environment: a denied TCP connect now returns
ECONNREFUSED in 3 ms (nftables reject with tcp reset), and the crash still occurs.
Black-holing (drop) crashes identically.
CI=1. Set for the process; no effect.
SHOPIFY_CLI_ENV=development. No effect on the crash in our matrix.
- There is no documented
SHOPIFY_CLI_* switch that disables the version check; the only guard in
the bundle is isPreReleaseVersion(version).
Impact
Any deployment that runs Pilot's dev server without npm-registry access — CI sandboxes, offline
development, restricted-egress hosting — cannot start the storefront at all. It is a hard startup
failure, not a degraded notification.
Suggested fix
Guard the version-check/self-upgrade flow so a failure (or a promise that settles before the
cancellation handler is attached) can never reject into the process: await it with a catch, or
skip it when the registry is unreachable. Pinning @shopify/cli past the race in Pilot's lockfile
would unblock consumers in the meantime.
DRAFT — not posted. For Paul's approval before anything goes to Weaverse/pilot.
Repo:
Weaverse/pilotTitle:
shopify hydrogen devaborts on startup in a network-restricted environment (@shopify/cli4.7.1 cancellation race)Summary
On Pilot
4a99511d(v2026.9.7),npm run devexits 1 during startup whenever the environment cannotreach the npm registry. The failure comes from
@shopify/cli's startup version-check path, not fromPilot code or from Vite/Hydrogen:
The message is
p-cancelable's guard: anonCancelhandler is attached to a promise that hasalready settled. The frame chain ends in
processTimers, so the throw is raised from a timer insidethe version-check/self-upgrade flow, escapes through
node:domain, and terminates the CLI processbefore the dev server ever listens. The dev server never prints
➜ Local:; the surrounding Miniflareinstance then reports
ERR_DISPOSEDas a consequence of the teardown.Environment
4a99511d(v2026.9.7), unmodified lockfile (@shopify/cli4.7.1; registry latest 4.8.0)node:26-slim), linux/arm64--porttaken from the environmentReproduction
npm cion a machine with network, sonode_modulesis complete.PORT=3458 npm run dev.Observed: exits 1 with the trace above. The CLI announces
✨ New version of Shopify CLI available! (4.7.1 → 4.8.0) Now upgrading by running: npm install -g @shopify/cli@latestwhen it can read the registry, so the crash sits on the path that learns about / installs a newer CLI.
Notably not the cause
ECONNREFUSEDin 3 ms (nftablesreject with tcp reset), and the crash still occurs.Black-holing (
drop) crashes identically.CI=1. Set for the process; no effect.SHOPIFY_CLI_ENV=development. No effect on the crash in our matrix.SHOPIFY_CLI_*switch that disables the version check; the only guard inthe bundle is
isPreReleaseVersion(version).Impact
Any deployment that runs Pilot's dev server without npm-registry access — CI sandboxes, offline
development, restricted-egress hosting — cannot start the storefront at all. It is a hard startup
failure, not a degraded notification.
Suggested fix
Guard the version-check/self-upgrade flow so a failure (or a promise that settles before the
cancellation handler is attached) can never reject into the process:
awaitit with acatch, orskip it when the registry is unreachable. Pinning
@shopify/clipast the race in Pilot's lockfilewould unblock consumers in the meantime.