Repository navigation
fix(cli): os doctor tells a tool missing from PATH apart from one that ran and failed - #24493
Conversation
…t ran and failed The pnpm, tsc and git probes answered "Not found" for every failure, so a Corepack-managed pnpm that could not download its version was told to reinstall pnpm. A failed probe now asks whether the tool is on the PATH it ran with (the shell's own lookup, PATHEXT on win32): absent keeps today's row, present reports the child's first error line with a fix that matches the cause. Statuses, and so the exit code, are unchanged. Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T Co-authored-by: Claude <noreply@anthropic.com>
…H holds nothing else Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 29 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3cbc166bfb839fd471b8dcbb9c05e52becb422ea && git checkout 3cbc166bfb839fd471b8dcbb9c05e52becb422ea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 33a45ccaddaf479256f78341e58b38727add9de4 023dd79d042e93b8fae0cfa89c72aa1b085b543d && git checkout -B drift-repro 33a45ccaddaf479256f78341e58b38727add9de4 && git merge --no-ff 023dd79d042e93b8fae0cfa89c72aa1b085b543d
node scripts/docs-audit/affected-docs.mjs --json 33a45ccaddaf479256f78341e58b38727add9de4
|
席位核对 — ACCEPT
Generated by Claude Code |
Fixes #24464
Clause-②: no. Basis: one failure class of three
os doctorrows changes its message and fix text. No accept set, package export, flag, row status or exit code moves.os doctorprobespnpm -v,tsc -vandgit --version, and each probe'scatchreported every failure as "Not found". Apnpmthat is installed but fails when it runs was told to reinstall pnpm. This PR tells "not on PATH" apart from "ran and failed". The first keeps today's row. The second reports the child's own first error line, with a fix that matches the cause.Before and after, measured
os doctorfrom the source entry (tsx packages/cli/bin/run-dev.js doctor), in an emptytmpdir()project, run withenv -i(onlyHOME,NO_COLOR=1and thePATHnamed per case). Before =packages/cli/src/commands/doctor.tsat the basee11ae4b6b. After = this branch at023dd79d0. Only the probe rows are quoted. Every other row was identical within each pair.1. No
pnpmon PATH (PATH= a directory holding only anodesymlink). stdout and stderr are byte-identical before and after. Exit 1 both times:2. A Corepack
pnpmwhose registry answers 429. This is real Corepack 0.34.0: shims fromcorepack enable --install-directory DIR pnpm, a freshCOREPACK_HOME, andCOREPACK_NPM_REGISTRYpointed at a local server that answers every request with429. No external network was reached. Exit 1 both times.Before:
After:
3. A
pnpmthat fails for another reason. A stub printsERR_PNPM_UNSUPPORTED_ENGINE Unsupported environment (bad pnpm and/or Node.js version)to stderr and exits 1. Exit 1 both times. Before: the sameNot foundand install fix as case 2. After:4. Control, a working toolchain (
PATH=/opt/node22/bin:/usr/bin:/bin). stdout and stderr are byte-identical before and after:✓ pnpm Version 10.28.0,✓ TypeScript Version 6.0.2,✓ Git git version 2.43.0.The exit code was 1 in all eight runs (the empty project also has no
node_modules, anerrorrow). Thepnpmrow stayserror, and theTypeScriptandGitrows staywarning, so no exit code moves. In cases 2 and 3 the summary line under❌ Some critical issues foundchanges with the fix, because it prints each error's fix. The child's stderr is still echoed above the report: the probe'sexecSynccall is unchanged.The classifier, and why it is shaped this way
Measured on Node 22.22 with
/bin/sh= dash, through the probe's own call,execSync(cmd, { encoding: 'utf-8' }):error.codestatuspnpmon PATH/bin/sh: 1: pnpm: not foundpnpmon PATH, itsnodemissing/usr/bin/env: 'node': No such file or directorypnpmon PATH, not executable/bin/sh: 1: pnpm: Permission deniedpnpm, registry unreachable.../corepack.cjs:22051, thenError: Error when performing the request to ...pnpm, registry answers 429.../corepack.cjs:22058, thenError: Server answered with HTTP 429 when performing the request to ...COREPACK_ENABLE_NETWORK=0Network access disabled by the environment; can't reach npm repository ...ENOENT.classifyToolProbeFailureasks the question directly: is there a file namedpnpmon thePATHthe probe ran with, where the shell would have looked? If not, the outcome isnot-found, and the row is today's, byte for byte. If so, the outcome isfailed.failedrow's message is the child's first meaningful line. That is the first...Error:line if there is one, because an uncaught Node error opens withFILE:LINE, the source line and a caret. Otherwise it is the first non-blank stderr line, then stdout's. With no output at all, it reads`pnpm -v` exited with code N with no output.fetch. It fires onwhen performing the request totogether withcorepackin the stderr (both the no-answer and the HTTP-status shapes), or onNetwork access disabled by the environment; can't reach. Any other failure gets the neutral fix.Windows.
execSyncrunscmd.exe /d /s /c "pnpm -v". A missingpnpmthere answers "'pnpm' is not recognized as an internal or external command" (localised) with exit 1, the same code a failingpnpm.cmdexits with. Neither the code nor the text can tell them apart.execFileSync('pnpm', ...)without a shell does not findpnpm.cmd, so dropping the shell is not an option either. The lookup'swin32branch therefore followscmd.exe's resolution:PATHentry, withPATHandPATHEXTread case-insensitively;PATHEXTextension in turn;I could not run Windows here. Four unit cases drive that branch with
platform: 'win32'on Linux:pnpm.cmdonPathis found;pnpmis not counted (and the POSIX branch does count it);not-foundwhen no candidate exists, andfailedwhen apnpm.cmddoes.Family census:
tsc -vandgit --versionBoth moved to the same classifier. Nothing changes except the message and the fix:
tsc -v(TypeScript,warning). Not on PATH keepsNot found in PATH/Installed locally via pnpm. On PATH and failing, it reports the child's line withtsc is on PATH, but `tsc -v` exited with code 1: run `tsc -v` and read its error. One example is the npmtscpackage, which is not TypeScript: it prints "This is not the tsc command you are looking for" and exits 1. The row is still awarning.git --version(Git,warning). Not on PATH keepsNot found/Install Git for version control. On PATH and failing, it reports the child's line with the neutral fix. One example is macOS's/usr/bin/gitwithout the developer tools, which printsxcrun: error: invalid active developer path .... The row is still awarning.doctor.tscheck changed.Tests
packages/cli/test/doctor-tool-probe-failure.test.tsis new. It is in theunittier and imports nochild_process. It has 17 cases. Each row case runs the real probe:pnpmCheck,typescriptCheckandgitCheckrunexecSyncthrough the shell, asos doctordoes.PATHis a directory the test controls, either empty or holding one#!/bin/shstub that uses only shell builtins. Nothing else is on thatPATH, so no realpnpm, Corepack or registry is reachable.pnpm: not on PATH (today's row, asserted whole); a Corepack-shaped 429 failure (Corepack's error line, the Corepack fix); another failure (its line, the neutral fix); present but exiting 127; present but not executable; no output; and a working control.tscandgit: not on PATH (today's rows), and present but failing.win32lookup cases above,firstMeaningfulLine, andisCorepackFetchFailure(which stays false for a CorepackUsage Errorand for the request phrase with nocorepackin the stderr).Commands and results, all on
023dd79d0:pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/doctor-tool-probe-failure.test.ts: exit 0,Tests 17 passed (17).pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: exit 0,Test Files 312 passed (312),Tests 4627 passed (4627). Theintegrationtier is left to CI: this diff touches no spawn entry and no integration-tier file.pnpm --filter @objectstack/cli typecheck: exit 0. The new test is in thetsconfig.test.jsonprogram (tsc --listFilesOnlycounts it).pnpm --filter @objectstack/cli build: exit 0..tsfiles: exit 0, 0 errors, 0 warnings, neither file ignored.Ablation (one-off, no permanent file). The fix was committed first.
scripts/ablation-replace.mjsreplacedif (!isOnShellPath(binary, options)) return { outcome: 'not-found' };withif (binary) return { outcome: 'not-found' };, which is always true here: today's catch-all. The anchor went 1 to 0, the replacement 0 to 1, and the blob changed (7d2cb9411fc8toa59ad6a54cbc). With that in place the new file gaveTests 8 failed | 9 passed (17). The 8 failures are exactly the "ran and failed" cases (Corepack 429, other failure, exit 127, not executable, no output, thetscimpostor, thegitxcrun stub, and thewin32classification). The 9 passes are the not-found rows, the control and the pure helpers. The restore was verified: blob equal to HEAD andgit diff HEADempty. The test imports../src/commands/doctorrelatively, so it reads source, notdist/, and no build leg was needed.Gates
node scripts/pm/dispatch-gates.mjs --commands: 68 derived families. Each was run with its exit code captured before any pipe. 66 exited 0 on the first run.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst exited 3 (PREREQUISITE NOT MET, unbuilt packages outside the CLI's dependency closure). Re-run after those packages were built, both exited 0.dispatch-gates.mjs --ran:68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.check:init-service-contract,check:live-db-isolation,check:meta-type-normalized,check:optional-error-sink,check:resume-authority-declared,check:route-envelope,check:runner-env-posture,check:settings-bind-window,check:startup-registry-verdict,check:verify-stand-in,check:wildcard-fallthrough): all exited 0.pnpm lint(eslint . --no-inline-config, the repository-wide union), run after the final commit:VERDICT command-exit 0, no findings printed, on023dd79d0.Acceptance notes
Noted, not filed. No carrier exists for any of these.
pnpm -vthat hangs would hangos doctor. This was not observed: Corepack fails fast on a refused or rate-limited registry in every run above.pnpm@10.28.1, while this repository's ownpackageManagerispnpm@10.31.0. The "not found" row was required to stay unchanged, so the hint was not touched.origin/mainmoved four commits past this branch's base (4bd7215a1). Those commits touchpackages/lint,packages/spec,packages/plugins/plugin-security, docs and an ADR, and nothing underpackages/cli. The branch was not merged with them before opening; CI's merge ref covers the joint state.Generated by Claude Code