fix(doctor): detect missing native binary during runtime checks#450
fix(doctor): detect missing native binary during runtime checks#450michaelkillgta wants to merge 5 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughThe wrapper now routes missing-binary ChangesDoctor-shaped wrapper error report
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/tui/model.go (1)
777-796: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated os.Executable() resolution across CLI and TUI.
This 4-line "resolve exePath, default to empty on error" block is now duplicated verbatim in
internal/cli/observability.goand here. Given the existingLookupExecutablefield already establishes a "caller resolves, doctor verifies" pattern, this is consistent — but a tiny shared helper (e.g.doctor.ResolveExecutablePath()) would remove the duplication if a third caller shows up later.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/tui/model.go` around lines 777 - 796, The os.Executable() resolution logic is duplicated between the TUI doctor setup and the CLI observability path, so extract the “resolve executable path and fall back to empty on error” block into a shared helper such as doctor.ResolveExecutablePath() and use it from both call sites. Keep the existing doctor.Options wiring in internal/tui/model.go and the corresponding CLI code unchanged except for calling the shared helper, so the caller-resolves/doctor-verifies pattern stays consistent.internal/doctor/doctor_test.go (1)
553-574: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptionally assert
Details["verified"]in the warn test.The test correctly guards
Status != StatusPass/Warn, but doesn't assertcheck.Details["verified"] == falselike the fail-path test does at Line 526. Not blocking since the status assertion already covers the regression this test targets.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/doctor/doctor_test.go` around lines 553 - 574, The warn-path test for runtime.go should also verify the details payload, not just the status. In TestRuntimeCheckWarnsWhenExecutablePathUnset, assert that check.Details["verified"] is false, matching the existing fail-path coverage and using the same report.Check("runtime.go") result to confirm the warning state is represented consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/doctor/doctor_test.go`:
- Around line 553-574: The warn-path test for runtime.go should also verify the
details payload, not just the status. In
TestRuntimeCheckWarnsWhenExecutablePathUnset, assert that
check.Details["verified"] is false, matching the existing fail-path coverage and
using the same report.Check("runtime.go") result to confirm the warning state is
represented consistently.
In `@internal/tui/model.go`:
- Around line 777-796: The os.Executable() resolution logic is duplicated
between the TUI doctor setup and the CLI observability path, so extract the
“resolve executable path and fall back to empty on error” block into a shared
helper such as doctor.ResolveExecutablePath() and use it from both call sites.
Keep the existing doctor.Options wiring in internal/tui/model.go and the
corresponding CLI code unchanged except for calling the shared helper, so the
caller-resolves/doctor-verifies pattern stays consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fe1377c7-4290-4bd1-96ac-4f7b1675ed7b
📒 Files selected for processing (4)
internal/cli/observability.gointernal/doctor/doctor.gointernal/doctor/doctor_test.gointernal/tui/model.go
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Request changes — right instinct, but this fixes the wrong layer (and CI is red)
Thanks @michaelkillgta — #405 is a real papercut (doctor pointing users away from the actual cause), and the PR is thoughtful: clear comments, a fail-closed default, four focused tests. But as designed I don't think it fixes the reported bug, and it's currently failing CI.
1. The check can't observe the condition it targets (blocker)
The npm wrapper bin/zero.js (unchanged by this PR; guard already on main via #418) checks existsSync(nativePath) for every subcommand — including doctor — and on a missing binary prints "No native binary found" and exit 1 before it ever spawns the Go binary. So:
- In #405's exact repro (
ZERO_SKIP_DOWNLOAD=1 … zero doctor), the Go doctor code never runs — the newStatusFailbranch can't fire. - When Go doctor does run,
os.Executable()is the process executing it, soos.Staton it is a tautology → alwaysStatusPass. - The only reachable non-pass is
StatusWarnwhenos.Executable()itself errors — not the reported condition.
So a Go-side runtimeCheck architecturally can't see "the native binary is missing" (it can't be running when the binary is absent). The fix belongs in the JS wrapper's doctor path — e.g. a preflight, or have the wrapper pass doctor the expected nativePath so it can stat a path other than its own running image. If I'm missing a flow where zero doctor reaches Go with the binary absent, point me at it.
2. CI red — not gofmt-clean (blocker)
Smoke (ubuntu-latest) → Check formatting fails; I reproduced it — gofmt -l flags all four changed files (misaligned Details map literals in doctor.go). gofmt -w the four. (Windows/macOS smoke passing is misleading — the format gate is ubuntu-only.)
Minor
The os.Executable()→empty-on-error block is duplicated in observability.go and model.go, and no test covers the real call-site wiring (every test injects the fields).
Verified locally on Windows: build clean, internal/doctor tests pass — but they validate a mechanism that doesn't map to the real failure mode. Happy to re-review once detection moves to the wrapper layer and CI's green.
gnanam1990
left a comment
There was a problem hiding this comment.
VERDICT: needs-changes
REGRESSION RISK: medium
The change replaces the old unconditional StatusPass in runtimeCheck with a real os.Stat of the running binary path. The intent is correct (detect a missing native binary), but the implementation cannot observe the condition it targets: when the native binary is missing, bin/zero.js exits with "No native binary found" before spawning the Go process, so zero doctor never reaches the Go code. When Go doctor does run, os.Executable() returns the path of the currently executing binary, so os.Stat on it is a tautology that always passes. The only reachable non-pass state is StatusWarn when os.Executable() itself errors — which is not the reported condition (#405). The fix belongs in the JS wrapper's doctor path (e.g. have the wrapper pass the expected nativePath so Go doctor can stat a path other than its own running image), or a preflight check in the wrapper itself.
BUILD / TEST
go build ./...— passgo vet ./internal/doctor/... ./internal/cli/...— passgofmt -lon all four changed files — clean (the "style: run gofmt" commit addressed the gofmt blocker from the prior review)go test ./internal/doctor/... -run TestRuntimeCheck— all four tests pass, but they validate a mechanism that does not map to the real failure mode (every test injects the fields rather than exercising the real call-site wiring)- CI status for the latest commit (
b527640) could not be confirmed — the check-runs API returned empty for this commit, suggesting CI may not have re-run after the latest push. The prior commit's CI was red on the Ubuntu formatting gate, which is now fixed.
CONTRIBUTING
Author is a community contributor (michaelkillgta). The PR links Fixes #405, which has a bug label but no issue-approved label. Per CONTRIBUTING.md, the issue-approved gate applies to community PRs. Not met. Process violation, but reviewing the code regardless as instructed.
FINDINGS
-
Blocker —
internal/doctor/doctor.go:127-170— the check cannot observe the condition it targets.os.Executable()returns the path of the running Go binary, which by definition exists (it is executing).os.Staton it is alwaysStatusPass. When the binary is actually missing, the JS wrapper (bin/zero.js, unchanged by this PR) exits before the Go process starts, so the newStatusFailbranch is unreachable for the reported scenario. The fix needs to either (a) move detection to the JS wrapper's doctor path, or (b) have the wrapper pass the expectednativePathto the Go doctor so it can stat a path other than its own image. -
Minor —
internal/cli/observability.go:67-70andinternal/tui/model.go:777-790— duplicatedos.Executable()resolution. The 4-line "resolve exe path, empty on error" block is duplicated across CLI and TUI call sites. Consider a shared helper. No test covers the real call-site wiring (every test injects the fields).
EXISTING REVIEWS
- Vasanthdev2004 (CHANGES_REQUESTED on
81d37c5): Identified both blockers — (1) the check being a tautology / unreachable for the reported condition, and (2) CI red from gofmt. Two new commits have since been pushed (c1dd620"style: remove trailing blank line" andb527640"style: run gofmt"). Blocker #2 (gofmt) is now resolved — verified clean on the current head. Blocker #1 (architectural) is NOT addressed —bin/zero.jsis not in the changed files, and theos.Executable()tautology remains. The review is partially stale (gofmt fixed) but the primary blocker is still valid on the current head. - coderabbitai (APPROVED + 2 nitpicks): Flagged the duplicated
os.Executable()resolution. Valid, trivial. The approval did not catch the architectural blocker — the check cannot observe the missing-binary condition because the JS wrapper exits first.
BOTTOM LINE
The gofmt blocker is fixed, but the architectural blocker remains: a Go-side os.Executable() stat cannot detect a missing native binary because the binary is either running (tautology) or absent (Go never starts); the fix needs to move to the JS wrapper layer.
b527640 to
ed854d1
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
bin/zero.js (2)
59-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded doctor report text can silently drift from the real
doctor.Formatoutput.This block re-implements the Go-side report shape (header,
Overall: fail,[fail] runtime.go - ...) entirely in JS, independent ofinternal/doctor/doctor.go's renderer (context snippet 2/3). If the check ID, message wording, or "Overall:" formatting changes on the Go side, this wrapper fallback will silently diverge and no longer match what users see when the binary is present — undermining the goal of consistent doctor UX.Consider adding a lightweight cross-check (e.g., a test asserting the literal strings here match constants/messages defined in
doctor.go) so future changes to the Go check don't quietly break this fallback.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bin/zero.js` around lines 59 - 89, The doctor fallback text in zero.js is hardcoded and can drift from the Go renderer, so keep it aligned with internal/doctor/doctor.go. Update the zero doctor branch to use shared constants or add a test that compares the JS fallback strings against the doctor.Format output and runtime.go check message so any future wording or format changes fail fast. Reference the isDoctor branch and the zero doctor report block when making the change.
78-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBun remedy block duplicates the existing generic-bail logic.
This
ranByBun/ trustedDependencies remedy text appears to replicate structure already used by the pre-existing generic wrapper error (implied by the sharedranByBun/postinstallScriptvariables). Extracting this into a shared helper (e.g.,bunRemedyText()) would avoid two copies drifting apart when the messaging is updated.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bin/zero.js` around lines 78 - 84, The Bun remedy text is duplicated in the postinstall error path, which risks the trustedDependencies messaging drifting from the generic bail logic. Extract the `ranByBun`/trustedDependencies message assembly into a shared helper such as `bunRemedyText()` and have the existing error formatting use that helper wherever the Bun-specific remedy is appended. Keep the shared text in one place so both the generic wrapper error and the Bun-specific branch stay consistent.internal/npmwrapper/npmwrapper_test.go (1)
331-337: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing explicit exit-code assertion.
Unlike
TestNodeWrapperDoctorReportsMissingNativeBinaryAsDoctorFail, this test only checkserr == niland doesn't assertexitErr.ExitCode() == 1. A non-1 nonzero exit (e.g. a Node crash) would pass this check without being caught.✅ Proposed fix
if err == nil { t.Fatalf("doctor --connectivity should not exit 0 when binary missing: %s", output) } + exitErr, ok := err.(*exec.ExitError) + if !ok || exitErr.ExitCode() != 1 { + t.Fatalf("doctor --connectivity err = %v, want exit 1; output: %s", err, output) + } got := string(output)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/npmwrapper/npmwrapper_test.go` around lines 331 - 337, The test in the doctor connectivity case only checks that command.CombinedOutput returns a non-nil error, so it can miss the wrong failure mode. Update this assertion path in TestNodeWrapperDoctor… to also inspect the returned error as an ExitError and verify the exit code is exactly 1, matching the behavior already checked in TestNodeWrapperDoctorReportsMissingNativeBinaryAsDoctorFail. Use the existing command.CombinedOutput result and the test’s error handling to fail when the process exits nonzero for any reason other than the expected doctor failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bin/zero.js`:
- Around line 68-89: The missing-binary branch in bin/zero.js currently always
prints the human-readable doctor message and exits, which breaks the supported
zero doctor --json mode. Update the isDoctor handling so it detects argv entries
for --json and either emits a machine-readable error payload in the same
native-binary-missing path or explicitly rejects JSON mode with a clear CLI
error. Keep the fix localized around the doctor branch and the existing
postinstallScript/ranByBun logic so both plain-text and JSON doctor invocations
behave consistently.
---
Nitpick comments:
In `@bin/zero.js`:
- Around line 59-89: The doctor fallback text in zero.js is hardcoded and can
drift from the Go renderer, so keep it aligned with internal/doctor/doctor.go.
Update the zero doctor branch to use shared constants or add a test that
compares the JS fallback strings against the doctor.Format output and runtime.go
check message so any future wording or format changes fail fast. Reference the
isDoctor branch and the zero doctor report block when making the change.
- Around line 78-84: The Bun remedy text is duplicated in the postinstall error
path, which risks the trustedDependencies messaging drifting from the generic
bail logic. Extract the `ranByBun`/trustedDependencies message assembly into a
shared helper such as `bunRemedyText()` and have the existing error formatting
use that helper wherever the Bun-specific remedy is appended. Keep the shared
text in one place so both the generic wrapper error and the Bun-specific branch
stay consistent.
In `@internal/npmwrapper/npmwrapper_test.go`:
- Around line 331-337: The test in the doctor connectivity case only checks that
command.CombinedOutput returns a non-nil error, so it can miss the wrong failure
mode. Update this assertion path in TestNodeWrapperDoctor… to also inspect the
returned error as an ExitError and verify the exit code is exactly 1, matching
the behavior already checked in
TestNodeWrapperDoctorReportsMissingNativeBinaryAsDoctorFail. Use the existing
command.CombinedOutput result and the test’s error handling to fail when the
process exits nonzero for any reason other than the expected doctor failure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2bafdb30-474b-4d9f-a932-b375c4bb6131
📒 Files selected for processing (2)
bin/zero.jsinternal/npmwrapper/npmwrapper_test.go
jatmn
left a comment
There was a problem hiding this comment.
I found a couple of issues that need to be addressed before this is ready.
Findings
-
[P2] Complete CodeRabbit's request to preserve
doctor --json
bin/zero.js:68
CodeRabbit's latest review thread is still unresolved and still applies: the new missing-binarydoctorbranch treats everyzero doctor ...invocation as text output. The Go command already supportszero doctor --jsonand writes the report as JSON, but with the native binary missing this wrapper path now exits before Go starts and prints the human-readable report to stderr with empty stdout. That breaks scripts or CI using the machine-readable doctor mode exactly when diagnostics are needed most. Please either emit a JSON-shaped failure fordoctor --jsonin this fallback or reject JSON mode explicitly, and add wrapper coverage for that flag. -
[P3] Update the PR description to match the wrapper-layer fix
PR description
The current PR body still describes the older Go-side approach: it says the solution addsExecutablePathandStatExecutabletodoctor.Options, changesruntimeCheck, and addsTestRuntimeCheck...coverage. The current patch no longer touches those files; it only changesbin/zero.jsandinternal/npmwrapper/npmwrapper_test.go. Since prior review rejected the Go-side approach as the wrong layer, leaving that stale description and test plan makes it hard to tell what behavior this PR is actually asking maintainers to merge. Please update the summary, root cause, solution, and test plan to describe the current wrapper fallback.
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the update. I rechecked the current wrapper-layer patch and found a couple of issues that need to be addressed before this is ready.
Findings
-
[P2] Keep the text doctor report on stdout
bin/zero.js:95
The new missing-binaryzero doctorfallback writes the doctor-shaped text report withconsole.error, sozero doctor > report.txtcaptures an empty file even though the normal Go doctor path writesdoctor.Format(report)to stdout and uses the exit code to signal failure. The new regression test usesCombinedOutput, so it cannot catch this stream drift. Please write the text doctor report to stdout, like the JSON fallback and the regular doctor command do, while preserving exit code 1. -
[P3] Preserve doctor help and flag parsing in the wrapper fallback
bin/zero.js:68
The new branch treats every first-argumentdoctorinvocation as a runtime diagnostic, sozero doctor --helpandzero doctor helpnow return the missing-binary failure report instead of the doctor usage text, andzero doctor --json --bogusreturns a runtime JSON report instead of the normal unknown-flag error. The GorunDoctorpath handles help before running checks and rejects unknown flags, so the missing-binary wrapper should keep those contracts fordoctorrather than treating help and invalid invocations as real diagnostics. -
[P3] Resolve the contribution-policy gate for the linked issue
CONTRIBUTING.md
This PR linksFixes #405, but that issue currently has noissue-approvedlabel.CONTRIBUTING.mdsays community pull requests need an approved parent issue and that approval is shown by theissue-approvedlabel. Please get the parent issue approved/labeled or have a maintainer explicitly waive that gate before continuing the code review cycle. -
[P3] Update the PR description to match the wrapper-layer fix
PR description
The current PR body still describes the older Go-side approach: addingExecutablePathandStatExecutabletodoctor.Options, changingruntimeCheck, and addingTestRuntimeCheck...coverage. The current patch only changesbin/zero.jsandinternal/npmwrapper/npmwrapper_test.go, and prior review rejected the Go-side approach as the wrong layer. Please update the summary, root cause, solution, and test plan so the PR describes the wrapper fallback that is actually being reviewed.
|
Hi! I've addressed the latest review comments and updated the implementation to preserve CLI behavior (stdout for doctor reports, help/invalid-flag handling, JSON support). I also noticed the mention of the \issue-approved\ requirement. Could a maintainer let me know whether issue #405 should receive that label for this PR, or whether you'd prefer a different process? |
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the update. I rechecked the current wrapper-layer patch and found a couple of issues that still need to be addressed before this is ready.
Findings
-
[P1] Fix the failing Ubuntu smoke formatting check
internal/npmwrapper/npmwrapper_test.go:1
The currentSmoke (ubuntu-latest)check is failing in theCheck formattingstep, and the job log reportsgofmt needed on: internal/npmwrapper/npmwrapper_test.go. This is a changed file in this PR, so please rungofmton the test file and push the formatting fix before the code/test results can be treated as clean. -
[P3] Resolve the contribution-policy gate for the linked issue
CONTRIBUTING.md:19
This PR linksFixes #405, but that issue still has noissue-approvedlabel.CONTRIBUTING.mdsays community pull requests need an approved parent issue and that approval is shown by theissue-approvedlabel. Please get the parent issue approved/labeled or have a maintainer explicitly waive that gate before continuing the code review cycle.
|
Thanks for the re-review. P1 (gofmt): Addressed in \3082dfc\ (\style: gofmt npmwrapper doctor fallback tests). \gofmt -l internal/npmwrapper/npmwrapper_test.go\ is clean locally. The Ubuntu Smoke failure you saw should be resolved on that commit — could you re-run Smoke (ubuntu-latest) when you have a moment? (Fork workflow runs may need approval before checks appear on the latest push.) P3 (issue-approved): Understood. Issue #405 still doesn't have the \issue-approved\ label. I'm happy to pause the code-review cycle until a maintainer labels #405 or explicitly waives the gate per \CONTRIBUTING.md. Please let me know which you prefer. |
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the update. I rechecked the current wrapper-layer patch and found a couple of issues that still need to be addressed before this is ready.
Findings
-
[P2] Preserve the Bun trust-command recovery path in the doctor fallback
bin/zero.js:119
The new missing-binaryzero doctorfallback adds its own Bun-specific remedy, but it only tells Bun users to addtrustedDependencies. Currentmainalready updated the wrapper's missing-binary guidance to lead withbun pm trust @gitlawb/zeroandbun pm -g trust @gitlawb/zero, withtrustedDependenciesonly as the fallback for older Bun versions. This means Bun users who run the diagnostic command now get weaker, stale recovery advice than the generic missing-binary wrapper path. Please keep the new doctor-shaped report aligned with the current Bun guidance and add coverage for the Bun branch so it does not drift again. -
[P3] Resolve the contribution-policy gate for the linked issue
CONTRIBUTING.md:19
This PR still linksFixes #405, but that issue has noissue-approvedlabel.CONTRIBUTING.mdsays community pull requests need an approved parent issue and that approval is shown by theissue-approvedlabel. Please get the parent issue approved/labeled or have a maintainer explicitly waive that gate before continuing the code review cycle.
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the update. I rechecked the current wrapper-layer patch and found a few issues that still need to be addressed before this is ready.
Findings
-
[P1] Rebase on main and resolve the wrapper conflict first
GitHub mergeability
GitHub currently reports this branch asCONFLICTING/DIRTY, and the conflict is inbin/zero.js, which is the file this PR changes. Please rebase on the latestmainbranch and resolve that conflict before addressing the review findings below, so the missing-binary wrapper behavior is reviewed against the code that will actually be merged. -
[P2] Preserve the current Bun trust-command recovery guidance
bin/zero.js:74
The current PR head still builds the shared Bun recovery copy aroundtrustedDependenciesonly, but currentmainalready changed the missing-binary wrapper guidance to lead withbun pm trust @gitlawb/zeroandbun pm -g trust @gitlawb/zero, withtrustedDependenciesas the fallback for older Bun versions. After rebasing, please keep both the generic missing-binary path and the newzero doctorfallback on that newerbun pm trustguidance, and keep the Bun regression coverage pointed at that current copy so the doctor path cannot drift again. -
[P3] Resolve the contribution-policy gate for the linked issue
CONTRIBUTING.md:19
This PR still linksFixes #405, but that issue has noissue-approvedlabel.CONTRIBUTING.mdsays community pull requests need an approved parent issue and that approval is shown by theissue-approvedlabel. Please get the parent issue approved/labeled or have a maintainer explicitly waive that gate before continuing the code review cycle.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Extract bunRecoveryParagraph and related helpers so the doctor missing-binary text path and the generic wrapper bail share the same copy. Add regression tests with ZERO_WRAPPER_SIMULATE_BUN to guard against future drift. Co-authored-by: Cursor <cursoragent@cursor.com>
a5d9086 to
6a26a74
Compare
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the update. I rechecked the current wrapper-layer patch and found one issue that still needs to be addressed before this is ready.
Findings
- [P3] Resolve the contribution-policy gate for the linked issue
CONTRIBUTING.md:19
This PR still linksFixes #405, but issue #405 has noissue-approvedlabel.CONTRIBUTING.mdsays community pull requests need an approved parent issue and that approval is shown by theissue-approvedlabel. Please get the parent issue approved/labeled or have a maintainer explicitly waive that gate before this is treated as ready.
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Re-reviewing — my earlier request-changes was that this fixed the wrong layer, and that's resolved. The whole fix now lives in bin/zero.js, which is the only place that can actually observe a missing binary: the wrapper exits before the Go process ever starts, so nothing in the Go doctor could see it. The wrapper now special-cases zero doctor to print a doctor-shaped FAIL line for the runtime instead of the generic "no native binary" bail — which is exactly what #405 needed, since the complaint was that the one diagnostic command pointed users away from the real cause.
The stream handling is right, which is the thing that kept coming up: both the text report and the --json report go to stdout with exit 1 (so zero doctor > report.txt and --json | jq both work), unknown flags go to stderr with exit 2, and every non-doctor invocation keeps the original bail untouched. The Bun recovery leads with bun pm trust, matching main, and the test fixture captures stdout and stderr separately now, so it can catch the stream drift the old CombinedOutput test couldn't.
Marked #405 issue-approved — a doctor that greenlights a broken install is a real papercut, and this is the right fix. CI is green and it's mergeable.
Good to merge — over to kevin.
Fixes #405
Summary
When the native binary is missing next to the npm wrapper (
ZERO_SKIP_DOWNLOAD=1, failed postinstall, etc.),zero doctorpreviously gave users a false sense that setup was fine — or a generic wrapper error that did not look like a doctor report — whilezero execandzero providers listfailed with the real cause.Root cause
bin/zero.jsis the only entry point for npm-installedzero. When the native binary is absent, the wrapper exits before spawning Go. The old Go-sideruntimeCheck(os.Executable()+os.Stat()) could never observe this state: if Go is running, the binary already exists.Why the fix lives in the wrapper
The missing binary is detected in
bin/zero.jsatexistsSync(nativePath)— before any Go process starts. The wrapper must surface a doctor-shaped failure forzero doctorso the diagnostic command reports the actual problem instead of sending users to debug provider config or network issues.CLI compatibility
The wrapper mirrors
parseDoctorArgsandwriteDoctorHelpfrom the Go doctor command:zero doctorzero doctor --jsondoctor.ReportJSON on stdoutzero doctor --help/help/-hzero doctor --bogus[zero] unknown doctor flag "..."on stderrOnly valid doctor invocations use the missing-native-binary fallback. Help and invalid flags behave like the real CLI.
Tests
Added/updated wrapper tests in
internal/npmwrapper/npmwrapper_test.go:TestNodeWrapperDoctorReportsMissingNativeBinaryAsDoctorFail— text report on stdout, not stderrTestNodeWrapperDoctorJSONReportsMissingNativeBinaryAsJSONFail— JSON schema + stdout-onlyTestNodeWrapperDoctorHelpShowsUsage—--help,help,-hTestNodeWrapperDoctorRejectsUnknownFlag— unknown flag error on stderr, exit 2TestNodeWrapperDoctorWithFlagsStillReportsDoctorFail—--connectivitystill hits fallbackTestNodeWrapperReportsMissingNativeBinaryunchanged for non-doctor commandsTest plan
go test ./internal/npmwrapper/... -count=1git diff --check upstream/mainSummary by CodeRabbit
Bug Fixes
doctornow reliably returns a doctor-shaped failure report (stdout) includingruntime.go, a remedy that instructs runningpostinstall.mjs, and correct exit codes (--helpexit0, invalid flags exit2).doctor --jsonoutputs valid JSON withok: falseand a single failingruntime.gocheck; generic fallback messaging remains unchanged for non-doctorruns.doctor.Tests
doctor(standard/JSON/help/unknown flags) and Bun recovery copy consistency.