Skip to content

Trigger auto-upgrade when dev is quit via q or Ctrl+C#7554

Merged
alfonso-noriega merged 1 commit into
mainfrom
autopgrade-fails-in-app-dev
May 15, 2026
Merged

Trigger auto-upgrade when dev is quit via q or Ctrl+C#7554
alfonso-noriega merged 1 commit into
mainfrom
autopgrade-fails-in-app-dev

Conversation

@alfonso-noriega
Copy link
Copy Markdown
Contributor

@alfonso-noriega alfonso-noriega commented May 15, 2026

WHY are these changes introduced?

When a developer runs shopify app dev and quits the command via q or Ctrl+C, the CLI's auto-upgrade either never started or — more confusingly — started and got killed mid-stream. There were two distinct bugs combining to produce this:

  1. Dev.tsx (the legacy dev UI) hard-killed the process tree with a fixed 2 s setTimeout immediately after the abort fired. Once the postrun hook entered the auto-upgrade flow, the treeKill would race against it; if npm/pnpm/yarn install took longer than 2 s (almost always), the kill won and the upgrade was lost.

  2. DevSessionUI.tsx (the dev-session UI) tried to do better by polling postRunHookHasCompleted(). But the flag was being set in the postrun hook before autoUpgradeIfNeeded() was awaited — so as soon as the hook entered auto-upgrade the flag flipped to true, the poll fired, and treeKill ran while the upgrade was still in progress. (This explains the inconsistent reports of "auto-upgrade started — sometimes finished — sometimes didn't".)

Once the user aborts a dev command, useAbortSignal does eventually call complete() → Ink's exit()waitUntilExit() resolves → the dev run() returns → oclif fires the postrun hook → autoUpgradeIfNeeded() runs. The dev sub-processes (servers, watchers, …) keep the event loop alive after that, so the node process won't exit on its own — we still need to treeKill. We just need to do so after auto-upgrade actually finishes.

WHAT is this pull request doing?

Two changes in packages/cli-kit/src/public/node/hooks/postrun.ts:

  1. Move postRunHookCompleted = true to after autoUpgradeIfNeeded() resolves, so the flag accurately reflects "the postrun hook (including auto-upgrade) is done".
  2. Add a small helper waitForPostRunHookAndExit() that polls postRunHookHasCompleted() every 100 ms and, once true, treeKills the process tree and exits. A 120 s safety cap guarantees a stuck upgrade can't keep the dev process alive forever.

Both Dev.tsx and DevSessionUI.tsx now call waitForPostRunHookAndExit() from their abort handler instead of doing their own setTimeout / leaky polling. The corresponding unit tests mock waitForPostRunHookAndExit so the abort-path scenarios don't trigger real intervals or treeKill/process.exit.

Files changed:

  • packages/cli-kit/src/public/node/hooks/postrun.ts — flag-ordering fix + new waitForPostRunHookAndExit() helper.
  • packages/app/src/cli/services/dev/ui/components/Dev.tsx — use the helper, drop the 2 s setTimeout and the isUnitTest/treeKill/context/local imports.
  • packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx — use the helper, drop the leaky inline polling and the isUnitTest/treeKill/context/local imports.
  • packages/app/src/cli/services/dev/ui/components/Dev.test.tsx — mock the helper.
  • packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx — mock the helper.

How to test your changes?

  1. Make sure auto-upgrade has a target available locally — easiest: set SHOPIFY_CLI_FORCE_AUTO_UPGRADE=1 and ensure versionToAutoUpgrade() would return a newer version (e.g. by pointing the CLI at a stale version).
  2. Run shopify app dev against a sample app.
  3. Press q (or Ctrl+C) to quit.
  4. Expected: the dev UI prints Shutting down dev …, the auto-upgrade output appears and completes, and only then does the process exit. On main today the process exits before the upgrade can run, or the upgrade gets killed mid-install.
  5. Repeat against an app that goes through the dev-session path (DevSessionUI) — same expected behavior.

Post-release steps

N/A.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows) — only the existing treeKill path is reused.
  • I've considered possible documentation changes — none required.
  • I've considered analytics changes to measure impact — auto-upgrade success/skip metadata is unchanged.
  • 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

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@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 May 15, 2026
@alfonso-noriega alfonso-noriega force-pushed the autopgrade-fails-in-app-dev branch 2 times, most recently from c0444fc to dacb80b Compare May 15, 2026 09:32
@alfonso-noriega alfonso-noriega marked this pull request as ready for review May 15, 2026 09:37
@alfonso-noriega alfonso-noriega requested a review from a team as a code owner May 15, 2026 09:37
@alfonso-noriega
Copy link
Copy Markdown
Contributor Author

/snapit

1 similar comment
@alfonso-noriega
Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

🫰✨ Thanks @alfonso-noriega! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260515095956

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@alfonso-noriega alfonso-noriega force-pushed the autopgrade-fails-in-app-dev branch 3 times, most recently from 8165d1f to 629f2fe Compare May 15, 2026 11:40
@alfonso-noriega
Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

🫰✨ Thanks @alfonso-noriega! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260515114340

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.


// Run auto-upgrade *before* flipping the postRunHookCompleted flag so consumers that
// poll it (e.g. `waitForPostRunHookAndExit` from `app dev`) don't `treeKill` the
// process mid-upgrade.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this comment, it just the logical thing to have postRunHookCompleted = true at the end of the hook

// Wait for the oclif postrun hook (which triggers auto-upgrade) to finish before
// tree-killing the process tree, otherwise quitting via `q` or Ctrl+C skips
// auto-upgrade.
waitForPostRunHookAndExit()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the extra comments, seems like we are explaining the same thing in 3 different places.

waitForPostRunHookAndExit already has a description, anyone would understand what it does.

let's remove this comment and the one in Dev.tsx

The legacy dev UI (Dev.tsx) tore down the process tree with a fixed 2s
setTimeout immediately after the user pressed q or hit Ctrl+C. Because
oclif only invokes the postrun hook after run() resolves, exiting that
early meant the auto-upgrade flow never had a chance to run. Users on
the standard `shopify app dev` path therefore stopped receiving CLI
auto-upgrades after quitting dev.

DevSessionUI.tsx already polled postRunHookHasCompleted() before
exiting, but its setInterval was never cleared and the 5s ceiling was
too tight for an actual upgrade.

This change extracts the shutdown logic into a single helper,
waitForPostRunHookAndExit, in cli-kit's postrun module. Both Dev.tsx
and DevSessionUI.tsx now call it. The helper:

- polls postRunHookHasCompleted() and exits as soon as the hook is done,
- caps the wait at a configurable maxWaitMs (default 30s) so a stuck
  upgrade still terminates the process,
- properly clears its interval and guards against double-exit,
- delegates to the existing treeKill+process.exit path.
@alfonso-noriega alfonso-noriega force-pushed the autopgrade-fails-in-app-dev branch from 629f2fe to ed80b03 Compare May 15, 2026 12:51
@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

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/public/node/hooks/postrun.d.ts
@@ -1,7 +1,3 @@
-/**
- * Postrun hook — uses dynamic imports to avoid loading heavy modules (base-command, analytics)
- * at module evaluation time. These are only needed after the command has already finished.
- */
 import { Hook } from '@oclif/core';
 /**
  * Check if post run hook has completed.
@@ -9,6 +5,21 @@ import { Hook } from '@oclif/core';
  * @returns Whether post run hook has completed.
  */
 export declare function postRunHookHasCompleted(): boolean;
+/**
+ * Wait for the postrun hook to finish (so auto-upgrade has a chance to run) and then
+ * tree-kill the current process tree before exiting.
+ *
+ * Long-running interactive commands like  need this when the user terminates
+ * the command via  or Ctrl+C. The dev sub-processes such as servers and watchers keep
+ * the event loop alive, so even after oclif's postrun hook completes the node process
+ * won't exit on its own and we have to  the process tree. We must not do that
+ * before the postrun hook has actually finished running auto-upgrade, otherwise we would
+ * kill the upgrade mid-way while  is still running.
+ *
+ * The flag  is flipped at the very end of the hook after
+ *  resolves, so polling it here is safe.
+ */
+export declare function waitForPostRunHookAndExit(): void;
 export declare const hook: Hook.Postrun;
 /**
  * Auto-upgrades the CLI after a command completes, if a newer version is available.

@alfonso-noriega alfonso-noriega added this pull request to the merge queue May 15, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 15, 2026
@alfonso-noriega alfonso-noriega added this pull request to the merge queue May 15, 2026
Merged via the queue into main with commit 1aee8d0 May 15, 2026
26 of 28 checks passed
@alfonso-noriega alfonso-noriega deleted the autopgrade-fails-in-app-dev branch May 15, 2026 13:38
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