Before submitting
Area
apps/server
Steps to reproduce
- Install
@anthropic-ai/claude-code globally with npm 12 under nvm and confirm claude runs.
- In the desktop app, click the update control for the claude provider.
- Wait for the app to report the update as successful.
- Run
claude in a terminal.
Expected behavior
The app reports the update as successful, and every subsequent claude invocation fails with:
Error: claude native binary not installed.
I reproduced this twice, on different days, with the same result.
The update command comes from makeNpmGlobalProviderMaintenanceCapabilities in apps/server/src/provider/providerMaintenance.ts and runs through updateProvider in providerMaintenanceRunner.ts:
npm install -g @anthropic-ai/claude-code@latest
npm 12 blocks install scripts unless the package is listed in the allow-scripts config, which is empty by default. A blocked script produces only a warning and the install still exits 0. The package's postinstall (install.cjs) is what copies the platform-native binary over the bin/claude stub. The platform package downloads, the blocked script never runs, the stub stays in place, and npm removes the previously working tree while applying the change. The install ends broken with an exit code of 0. From the npm debug log of the actual update run:
6 verbose title npm install @anthropic-ai/claude-code@latest
7 verbose argv "install" "--global" "@anthropic-ai/claude-code@latest"
71 silly CHANGE node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-darwin-arm64
72 warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
72 warn install-scripts @anthropic-ai/claude-code@2.1.224 (postinstall: node install.cjs)
77 verbose exit 0
updateProvider treats a zero exit as success, so the user sees a successful update while claude is broken.
A one-line change in makeNpmGlobalProviderMaintenanceCapabilities fixes this and scopes the allowance to exactly the package being updated:
updateArgs: [
"install",
"-g",
`--allow-scripts=${definition.npmPackageName}`,
`${definition.npmPackageName}@latest`,
],
This is what npm's own warning prescribes. npm 11 already ships allow-scripts as a known config key and accepts the flag without complaint. I verified this on 11.17.0. Older npm treats it as unknown config, warns, and continues with exit 0. I verified that path on 11.17.0 with a deliberately unknown flag, and warn-and-continue is npm's long-standing behavior for unknown config.
The pnpm-global and bun-global update paths in the same file are likely exposed to the same class of failure, since pnpm 10 and newer and bun ship their own script-blocking defaults. I did not reproduce those. Separately, because the runner accepts any zero exit, every blocked-postinstall update reports success while the CLI is broken. Scanning stderr for the warn install-scripts marker would catch this class regardless of package manager.
Actual behavior
The app reports the update as successful, and every subsequent claude invocation fails with:
Error: claude native binary not installed.
I reproduced this twice, on different days, with the same result.
The update command comes from makeNpmGlobalProviderMaintenanceCapabilities in apps/server/src/provider/providerMaintenance.ts and runs through updateProvider in providerMaintenanceRunner.ts:
npm install -g @anthropic-ai/claude-code@latest
npm 12 blocks install scripts unless the package is listed in the allow-scripts config, which is empty by default. A blocked script produces only a warning and the install still exits 0. The package's postinstall (install.cjs) is what copies the platform-native binary over the bin/claude stub. The platform package downloads, the blocked script never runs, the stub stays in place, and npm removes the previously working tree while applying the change. The install ends broken with an exit code of 0. From the npm debug log of the actual update run:
6 verbose title npm install @anthropic-ai/claude-code@latest
7 verbose argv "install" "--global" "@anthropic-ai/claude-code@latest"
71 silly CHANGE node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-darwin-arm64
72 warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
72 warn install-scripts @anthropic-ai/claude-code@2.1.224 (postinstall: node install.cjs)
77 verbose exit 0
updateProvider treats a zero exit as success, so the user sees a successful update while claude is broken.
A one-line change in makeNpmGlobalProviderMaintenanceCapabilities fixes this and scopes the allowance to exactly the package being updated:
updateArgs: [
"install",
"-g",
`--allow-scripts=${definition.npmPackageName}`,
`${definition.npmPackageName}@latest`,
],
This is what npm's own warning prescribes. npm 11 already ships allow-scripts as a known config key and accepts the flag without complaint. I verified this on 11.17.0. Older npm treats it as unknown config, warns, and continues with exit 0. I verified that path on 11.17.0 with a deliberately unknown flag, and warn-and-continue is npm's long-standing behavior for unknown config.
The pnpm-global and bun-global update paths in the same file are likely exposed to the same class of failure, since pnpm 10 and newer and bun ship their own script-blocking defaults. I did not reproduce those. Separately, because the runner accepts any zero exit, every blocked-postinstall update reports success while the CLI is broken. Scanning stderr for the warn install-scripts marker would catch this class regardless of package manager.
Impact
Major degradation or frequent failure
Version or commit
main @ 5661c61
Environment
macOS arm64 (Darwin 25), Node v24.18.1, npm 12.0.2, @anthropic-ai/claude-code 2.1.224 installed npm-global under nvm
Logs or stack traces
6 verbose title npm install @anthropic-ai/claude-code@latest
7 verbose argv "install" "--global" "@anthropic-ai/claude-code@latest"
71 silly CHANGE node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-darwin-arm64
72 warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
72 warn install-scripts @anthropic-ai/claude-code@2.1.224 (postinstall: node install.cjs)
77 verbose exit 0
Screenshots, recordings, or supporting files
No response
Workaround
Run the skipped postinstall by hand:
node <global node_modules>/@anthropic-ai/claude-code/install.cjs
Before submitting
Area
apps/server
Steps to reproduce
@anthropic-ai/claude-codeglobally with npm 12 under nvm and confirmclauderuns.claudein a terminal.Expected behavior
The app reports the update as successful, and every subsequent
claudeinvocation fails with:I reproduced this twice, on different days, with the same result.
The update command comes from
makeNpmGlobalProviderMaintenanceCapabilitiesinapps/server/src/provider/providerMaintenance.tsand runs throughupdateProviderinproviderMaintenanceRunner.ts:npm 12 blocks install scripts unless the package is listed in the
allow-scriptsconfig, which is empty by default. A blocked script produces only a warning and the install still exits 0. The package's postinstall (install.cjs) is what copies the platform-native binary over thebin/claudestub. The platform package downloads, the blocked script never runs, the stub stays in place, and npm removes the previously working tree while applying the change. The install ends broken with an exit code of 0. From the npm debug log of the actual update run:updateProvidertreats a zero exit as success, so the user sees a successful update whileclaudeis broken.A one-line change in
makeNpmGlobalProviderMaintenanceCapabilitiesfixes this and scopes the allowance to exactly the package being updated:This is what npm's own warning prescribes. npm 11 already ships
allow-scriptsas a known config key and accepts the flag without complaint. I verified this on 11.17.0. Older npm treats it as unknown config, warns, and continues with exit 0. I verified that path on 11.17.0 with a deliberately unknown flag, and warn-and-continue is npm's long-standing behavior for unknown config.The pnpm-global and bun-global update paths in the same file are likely exposed to the same class of failure, since pnpm 10 and newer and bun ship their own script-blocking defaults. I did not reproduce those. Separately, because the runner accepts any zero exit, every blocked-postinstall update reports success while the CLI is broken. Scanning stderr for the
warn install-scriptsmarker would catch this class regardless of package manager.Actual behavior
The app reports the update as successful, and every subsequent
claudeinvocation fails with:I reproduced this twice, on different days, with the same result.
The update command comes from
makeNpmGlobalProviderMaintenanceCapabilitiesinapps/server/src/provider/providerMaintenance.tsand runs throughupdateProviderinproviderMaintenanceRunner.ts:npm 12 blocks install scripts unless the package is listed in the
allow-scriptsconfig, which is empty by default. A blocked script produces only a warning and the install still exits 0. The package's postinstall (install.cjs) is what copies the platform-native binary over thebin/claudestub. The platform package downloads, the blocked script never runs, the stub stays in place, and npm removes the previously working tree while applying the change. The install ends broken with an exit code of 0. From the npm debug log of the actual update run:updateProvidertreats a zero exit as success, so the user sees a successful update whileclaudeis broken.A one-line change in
makeNpmGlobalProviderMaintenanceCapabilitiesfixes this and scopes the allowance to exactly the package being updated:This is what npm's own warning prescribes. npm 11 already ships
allow-scriptsas a known config key and accepts the flag without complaint. I verified this on 11.17.0. Older npm treats it as unknown config, warns, and continues with exit 0. I verified that path on 11.17.0 with a deliberately unknown flag, and warn-and-continue is npm's long-standing behavior for unknown config.The pnpm-global and bun-global update paths in the same file are likely exposed to the same class of failure, since pnpm 10 and newer and bun ship their own script-blocking defaults. I did not reproduce those. Separately, because the runner accepts any zero exit, every blocked-postinstall update reports success while the CLI is broken. Scanning stderr for the
warn install-scriptsmarker would catch this class regardless of package manager.Impact
Major degradation or frequent failure
Version or commit
main @ 5661c61
Environment
macOS arm64 (Darwin 25), Node v24.18.1, npm 12.0.2,
@anthropic-ai/claude-code2.1.224 installed npm-global under nvmLogs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
Run the skipped postinstall by hand: