fix(plugins): surface npm errors when plugin install fails [AI-assisted]#73093
fix(plugins): surface npm errors when plugin install fails [AI-assisted]#73093sanctrl wants to merge 2 commits intoopenclaw:mainfrom
Conversation
`openclaw plugins install <pkg>` reports failures as literally `npm install failed:` with no detail, leaving users no way to diagnose registry 4xx, ERESOLVE, EUNSUPPORTEDPROTOCOL, network timeouts, or any other npm failure. Root cause: `runCommandWithTimeout` is invoked with `--silent` in the argv at `src/infra/install-package-dir.ts:261`. `--silent` suppresses both stdout and stderr below the `warn` level — npm exits non-zero but writes nothing to either stream. The failure handler at line ~278 then formats `"npm install failed: " + (stderr || stdout).trim()`, and with empty buffers that's the literal user-facing message. Fix: replace `--silent` with `--loglevel=error`. Keeps npm's normal info-level chatter suppressed in the user terminal (the original intent of `--silent`) but lets genuine error output through to the existing failure-message formatter. Single-token argv change; no other behavior altered. Tests: - Updated the two existing argv assertions in install-package-dir.test.ts (lines 328, 436) to match the new flag. - Added a new `surfaces npm's stderr in the failure message instead of swallowing it` test that locks in the actual user-facing behavior: mocks `runCommandWithTimeout` to return a non-zero exit with an EUNSUPPORTEDPROTOCOL stderr (the real-world signature that surfaced this bug for `@agentchatme/openclaw@0.6.2`), and asserts the result error includes both the npm error code and the unsupported-spec fragment. Without the fix this test would have asserted only on the empty-colon prefix. What did NOT change (scope boundary): - Install staging, env-scrubbing (`createNpmProjectInstallEnv`), `--ignore-scripts`, `--omit=dev`, timeout, hidden-npmrc dance — all preserved.
Greptile SummaryThis PR fixes Confidence Score: 5/5Safe to merge — minimal, well-scoped change with correct logic and good regression coverage. Single-token flag swap with a clear semantic justification, updated tests, and a new regression test that locks in the corrected behavior. No P0 or P1 issues found. No files require special attention. Reviews (1): Last reviewed commit: "fix(plugins): surface npm errors when pl..." | Re-trigger Greptile |
|
Thanks for the focused fix. I verified the failure mode on Blacksmith Ubuntu / Node 24 / npm 11: Landed this on Validation run before push:
Closing this PR as covered by the landed commit. Thanks @sanctrl. |
Summary
openclaw plugins install <pkg>reports failures as literallynpm install failed:with no detail, leaving users no way to diagnose registry 4xx, ERESOLVE, EUNSUPPORTEDPROTOCOL, network timeouts, etc.--silentwith--loglevel=errorin the argv atsrc/infra/install-package-dir.ts:261. Single-token change. Updated the two existing argv assertions ininstall-package-dir.test.tsand added a new failure-path regression test that locks in the user-facing behavior.createNpmProjectInstallEnv),--ignore-scripts,--omit=dev, timeout handling, hidden-.npmrcdance — all preserved.Change Type
Scope
Linked Issue/PR
(No tracking issue — per
CONTRIBUTING.md"Bugs & small fixes -> Open a PR!")Root Cause
runCommandWithTimeoutis invoked with--silentin the argv.--silentsuppresses both stdout and stderr below thewarnlevel — npm can exit non-zero but write nothing to either stream. The failure handler formats"npm install failed: " + (stderr || stdout).trim(), and with empty buffers that's the literal output.installPackageDir's npm-install branch. The two existing tests assert only the spawned argv (success path).--silentwas almost certainly originally added to suppress npm's info-level chatter in the user terminal — a reasonable UX goal that has the same surface-area effect as--loglevel=errorbut a much wider blast radius (kills error output too). Replacing with--loglevel=errorpreserves the quiet-stdout intent while letting genuine errors through.Regression Test Plan
src/infra/install-package-dir.test.ts:surfaces npm's stderr in the failure message instead of swallowing it.src/infra/install-package-dir.test.tsrunCommandWithTimeoutreturns{ code: 1, stderr: "npm error code EUNSUPPORTEDPROTOCOL\nnpm error Unsupported URL Type \"workspace:\": workspace:^\n" }and the resultinginstallPackageDirfailure message must contain bothEUNSUPPORTEDPROTOCOLandworkspace:(i.e. the real npm error has actually been surfaced, not swallowed).(stderr || stdout)formatting) fails this test before merge.User-visible / Behavior Changes
openclaw plugins installnow prints the actual npm error afternpm install failed:(e.g.EUNSUPPORTEDPROTOCOL,ERESOLVE, network timeout, registry 4xx) instead of an empty string.openclaw plugins installis unchanged —--loglevel=errorsuppresses the same info-level chatter--silentdid.Diagram
Security Impact
Repro + Verification
Environment
@agentchatme/openclaw@0.6.2(community plugin) via ClawHub source-linked~/.openclaw, no env overridesSteps
openclaw plugins install @agentchatme/openclaw@0.6.2(this version had aworkspace:^spec in its source-treepackage.jsonthat ClawHub source-linked builds shipped verbatim — npm rejects it withEUNSUPPORTEDPROTOCOL).~/.npm/_logs/<latest>.logto recover the swallowed stderr.Expected (after this PR)
npm install failed: npm error code EUNSUPPORTEDPROTOCOL ...Actual (before this PR)
npm install failed:and nothing after the colon. Recovering the real error requires reading~/.npm/_logs/<latest>.logmanually.Evidence
Failing test before / passing after — the new
surfaces npm's stderr in the failure message instead of swallowing ittest fails onmain(assertsEUNSUPPORTEDPROTOCOLin the result error, which would be empty under--silent) and passes on this branch.Trace/log snippets — recovered npm log from a real failed install (the user-side reproducer) attached:
Today this content is in
~/.npm/_logs/<latest>.logonly; after this PR, OpenClaw surfaces it in the install command's stderr too.Human Verification
pnpm exec vitest run src/infra/install-package-dir.test.tslocally — 8/8 tests pass on this branch (vs. 7/8 + 1 newly-added on main, which would fail without the fix).--loglevel=errorsemantics:npm installwarnings are still emitted atwarnand above, errors aterrorand above (npm docs default iswarn,--silentis belowerror,errorlets warn-level through if upstream changes). Did not need to verify the warning-suppression edge —--silentwas the failure mode, not the warning level.pnpm testmatrix (the targeted vitest run covers the modified file). Did not runpnpm check(the umbrella lint/typecheck) — happy to address findings if CI surfaces any.Review Conversations
(Will fill in after submitting if any bot conversations land.)
Compatibility / Migration
Risks and Mitigations
--loglevel=errorlets warning-level output through where--silentdid not (different surface from errors).--loglevel=erroractually only emitserror-and-above; warnings are atwarnlevel and would require--loglevel=warn. So this is strictly tighter thandefaultand looser than--silentonly in the error category — no unrelated chatter is unblocked.npm install failed:prefix.AI-assisted
This PR was AI-assisted (Claude Code). The author reviewed every line, ran the targeted test suite, and confirmed the behavior matches the documented intent before submitting. Degree of testing: lightly tested — targeted vitest run on the modified file plus end-to-end manual verification on the GCE VM that originally hit the bug; full
pnpm check/pnpm buildnot yet run from this machine due to memory pressure — happy to iterate on CI findings.