Recover gracefully when pnpm blocks build scripts during extension generation - #8491
Open
amcaplan wants to merge 2 commits into
Open
Recover gracefully when pnpm blocks build scripts during extension generation#8491amcaplan wants to merge 2 commits into
amcaplan wants to merge 2 commits into
Conversation
…neration When generating an extension in a pnpm app, recent pnpm versions fail the non-interactive dependency install with ERR_PNPM_IGNORED_BUILDS because the build-script approval prompt can't be answered from within the generation tasks. The raw install error was surfaced as-is, and if the cleanup of the partially generated extension failed, the cleanup error masked it and left files behind. Now the failure is mapped to an actionable error (run pnpm approve-builds, then generate again), and the cleanup is best-effort: a cleanup failure warns about the leftover directory instead of replacing the original error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Assisted-By: devx/9ab0e8f6-64e1-4036-bcd3-470ba040dcdf
4 tasks
amcaplan
force-pushed
the
graceful-pnpm-blocked-builds-on-generate
branch
from
September 7, 2026 21:13
af395f8 to
c32dda4
Compare
amcaplan
marked this pull request as ready for review
September 7, 2026 21:17
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The changes are well-scoped and validated with tests, with only minor nits identified.
Pull request overview
Improves the extension generation flow to handle pnpm’s “blocked build scripts” behavior gracefully, while making cleanup of partially generated extensions more resilient to transient filesystem locks.
Changes:
- Detects pnpm’s
ERR_PNPM_IGNORED_BUILDSfailure duringshopify app generate extensionand surfaces anAbortErrorwith recovery steps. - Updates
@shopify/cli-kit’sremoveFileto supportfs.rmretry options and uses them to best-effort clean up partially generated extensions. - Adds/updates tests and changesets to cover the new failure mode and cleanup behavior.
File summaries
| File | Description |
|---|---|
| packages/cli-kit/src/public/node/fs.ts | Adds retry options to removeFile by delegating to fs.promises.rm. |
| packages/app/src/cli/services/generate/extension.ts | Adds pnpm blocked-builds detection + resilient cleanup and user guidance. |
| packages/app/src/cli/services/generate/extension.test.ts | Adds tests for pnpm blocked-builds messaging and cleanup-failure warning behavior. |
| .changeset/remove-file-retry-options.md | Changeset for removeFile retry options addition. |
| .changeset/graceful-pnpm-blocked-builds-on-generate.md | Changeset for improved pnpm blocked-builds recovery UX. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+131
to
+132
| if (isPnpmBlockedBuildsError(error)) { | ||
| throw new AbortError( |
amcaplan
force-pushed
the
graceful-pnpm-blocked-builds-on-generate
branch
2 times, most recently
from
September 8, 2026 10:26
ade3613 to
d116228
Compare
A failed install can leave transient locks on the extension's node_modules (for example, from an antivirus scanning the freshly written files). Let removeFile pass maxRetries/retryDelay through to Node's fs.rm, which retries EBUSY/ENOTEMPTY/EPERM with linear backoff, and use that (10 retries, 100ms) when removing the partially generated extension before falling back to the leftover-directory warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Assisted-By: devx/9ab0e8f6-64e1-4036-bcd3-470ba040dcdf
amcaplan
force-pushed
the
graceful-pnpm-blocked-builds-on-generate
branch
from
September 8, 2026 10:30
d116228 to
46dcc14
Compare
Contributor
Differences in type declarationsWe 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:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/public/node/fs.d.ts@@ -114,12 +114,26 @@ export declare function mkdir(path: string): Promise<void>;
* @param path - Path to the directory to be created.
*/
export declare function mkdirSync(path: string): void;
+interface RemoveFileOptions {
+ /**
+ * Number of times Node retries the removal when it hits a transient error
+ * (EBUSY, EMFILE, ENFILE, ENOTEMPTY or EPERM), waiting `retryDelay` milliseconds
+ * longer on each try. Defaults to 0 (no retries).
+ */
+ maxRetries?: number;
+ /**
+ * Milliseconds to wait between retries. Defaults to 100.
+ */
+ retryDelay?: number;
+}
/**
- * Removes a file at the given path.
+ * Removes a file or directory (recursively) at the given path.
*
- * @param path - Path to the file to be removed.
+ * @param path - Path to the file or directory to be removed.
+ * @param options - Retry behavior, passed through to Node's `fs.rm`. Useful when the removal can
+ * race with transient locks, such as an antivirus scanning freshly written files.
*/
-export declare function removeFile(path: string): Promise<void>;
+export declare function removeFile(path: string, options?: RemoveFileOptions): Promise<void>;
/**
* Renames a file.
* @param from - Path to the file to be renamed.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WHY are these changes introduced?
Fixes https://github.com/shop/issues-develop/issues/23837
Recent pnpm versions refuse to run the build scripts of newly installed dependencies until the user approves them, and they fail the install outright (
ERR_PNPM_IGNORED_BUILDS) when they can't prompt for approval. Sinceshopify app generate extensioninstalls dependencies from within the generation task UI (non-interactive stdio), the prompt can never be shown, so the install always fails on affected setups.Verified against pnpm 12.3.4: the install completes resolution and linking (creating
node_modulesinside the new extension directory) and then exits 1. Two problems with how we handled that:pnpm installfailure was surfaced as-is, with no guidance on how to recover.removeFile(directory)inextensionInit's catch) was unguarded — if the removal failed for any reason, its error replaced the original install error and the partially removed extension directory (e.g. just anode_modulesfolder) was left behind, matching the state reported in the issue.WHAT is this pull request doing?
In
packages/app/src/cli/services/generate/extension.ts(plus a small@shopify/cli-kitaddition):ERR_PNPM_IGNORED_BUILDS) and raises anAbortErrorwith next steps: runpnpm approve-buildsin the app directory, then generate the extension again. This works because the failed install records the ignored builds in the app root'snode_modules/.modules.yaml, sopnpm approve-buildslists them even after the extension directory is removed (verified locally).removeFilenow accepts optionalmaxRetries/retryDelayoptions passed straight through to Node'sfs.rm, which retries transient errors (EBUSY/ENOTEMPTY/EPERM — e.g. antivirus or indexer locks on the freshly writtennode_modules) with linear backoff, retrying the specific failing entry rather than restarting the walk. The cleanup uses{maxRetries: 10, retryDelay: 100}(~5.5s worst case). If removal still fails, a warning names the leftover directory and tells the user to delete it, and the error that actually interrupted the generation is still thrown.All generation flows (UI, function, theme; workspace and non-workspace installs) go through this catch, so they're all covered.
How to test your changes?
strictDepBuilds: true).onlyBuiltDependenciesentry).shopify app generate extensionand pick a JS-flavored template.Command failed with exit code 1: pnpm installdump. After: an error banner explaining pnpm blocked the build scripts, withpnpm approve-builds+ regenerate as next steps, and no leftoverextensions/<name>directory.Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add🤖 Generated with Claude Code