Skip to content

fix(cli): exit non-zero for user-error/startup surfaces riding oclif.exit === 0 (#5974) - #5986

Merged
cv merged 12 commits into
mainfrom
fix/5974-exit-code-hygiene
Jul 3, 2026
Merged

fix(cli): exit non-zero for user-error/startup surfaces riding oclif.exit === 0 (#5974)#5986
cv merged 12 commits into
mainfrom
fix/5974-exit-code-hygiene

Conversation

@yimoj

@yimoj yimoj commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Several nemoclaw user-error and unknown-command surfaces returned exit 0 even though they printed correct error text, which breaks $?-based scriptability (a watchdog or CI step wrapping the CLI could not detect the failure). This hardens the last structural exit-0 hole in the oclif runner and locks the reported surfaces with a regression matrix.

Related Issue

Closes #5974

Changes

Scope note: the per-command surfaces were re-tested on current main and already return non-zero (release drift since the v0.0.68 report); the matrix guards them against future regression, while the runner change closes the remaining catch-all path. The onboard startup paths (dashboard-port exhaustion, Python preflight) were verified to propagate as thrown errors through onboard's try/finally (no swallowing catch) and already exit non-zero, so they are left untouched. The share mount bad-remote-path diagnostic (#3414) needs a live sandbox + host sshfs to reach, so it stays covered by src/lib/share-command.test.ts / test/share-command-remote-path.test.ts rather than the hermetic spawn matrix.

Type of Change

  • Code change (feature, bug fix, or refactor)

Quality Gates

  • Tests added or updated for changed behavior
  • Docs not applicable — justification: no user-facing behavior or flag changes; only exit codes are corrected to be non-zero on already-documented error messages.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: CLI runner change is additive (only converts a wrongly-successful failure into a non-zero exit) and preserves legitimate graceful ExitError(0); covered by unit + integration tests.

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Git hooks passed during commit and push, or npx prek run --from-ref main --to-ref HEAD passes
  • Targeted tests pass for changed behavior
  • No secrets, API keys, or credentials committed

Reporter-workflow E2E (real worktree CLI)

Ran each reporter surface through the worktree binary ./bin/nemoclaw.js (Node entry → dist/nemoclaw.js) with an isolated HOME, a registry seeded with one sandbox (bug5974-alpha), and fake openshell/docker shims so no live gateway is contacted. Every command prints its error text and exits non-zero:

$ node ./bin/nemoclaw.js credentials reset
  Missing 1 required arg:
provider  OpenShell provider name
  => exit 2

$ node ./bin/nemoclaw.js bug5974-alpha skill install      # existing sandbox, missing path
  Missing 1 required arg:
skillPath  Skill directory or direct path to SKILL.md
  => exit 2

$ node ./bin/nemoclaw.js bug5974-alpha dcode --help        # existing sandbox, unknown action
  Unknown action: dcode
  Valid actions: agent, agents, channels, ... skill, snapshot, status, upload
  => exit 1

$ node ./bin/nemoclaw.js bug5974-missing-sb share mount /sandbox/bad-typo-path
  Sandbox 'bug5974-missing-sb' does not exist.
  => exit 1

$ node ./bin/nemoclaw.js bug5974-missing-sb upload some-file.txt
  Sandbox 'bug5974-missing-sb' does not exist.
  => exit 1

This exact reporter workflow is codified hermetically in test/exit-code-user-error-surfaces.test.ts, which spawns the same bin/nemoclaw.js and asserts non-zero exit + branch-specific error text for each row.

The behavioral fix itself lives in src/lib/cli/oclif-runner.ts (the oclif.exit === 0 catch-all that previously surfaced a message but reported success). That path is a defensive catch-all not directly reachable from a fixed user command, so it is covered by unit tests in src/lib/cli/oclif-runner.test.ts rather than a CLI transcript.

Other tests run locally:

  • vitest run --project cli src/lib/cli/oclif-runner.test.ts (11 passed)
  • vitest run --project integration test/exit-code-user-error-surfaces.test.ts (5 passed)
  • vitest run --project integration test/repro-2666-silent-list-status.test.ts (9 passed, no regression)
  • npm run typecheck:cli, npm run typecheck, biome lint + format, test title/size/overlap checks, prek run --from-ref main --to-ref HEAD — all pass.

Signed-off-by: Yimo Jiang yimoj@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Hardened CLI handling so errors that merely carry an exit code of 0 are treated as failures: they now surface a non-empty error message and exit with a non-zero status.
    • Preserved quiet behavior for genuine successful ExitError(0) exits.
  • Tests
    • Updated CLI runner tests to reflect the updated oclif mocking and the success-vs-failure exit/output distinctions.
    • Added end-to-end regression coverage for CLI error surfaces and dashboard port exhaustion.
    • Added a #5974 provider inference regression test to confirm failure propagation and logging.

A command's run() that throws an error merely carrying `oclif.exit === 0`
(not oclif's own graceful ExitError(0)) was surfaced by #2666 but still
reported success, so `$?` stayed 0 on a real failure and broke
scriptability. Treat only a genuine ExitError(0) as a silent graceful
exit; any other error on that channel now surfaces its message AND exits
non-zero, with the existing blank-message fallback preserved.

Add a hermetic regression matrix that runs the real `nemoclaw` binary
against fake openshell/docker shims and asserts the reported user-error /
unknown-command surfaces (credentials reset without a provider, skill
install without a path, unknown sandbox action, share mount / upload to a
nonexistent sandbox) print their error text and return a non-zero code.

Closes #5974

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d06a555b-ffb4-4191-b356-e67714dc2984

📥 Commits

Reviewing files that changed from the base of the PR and between e935388 and ffb91ad.

📒 Files selected for processing (2)
  • src/lib/onboard/machine/handlers/provider-inference.test.ts
  • test/exit-code-user-error-surfaces.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/exit-code-user-error-surfaces.test.ts

📝 Walkthrough

Walkthrough

runOclifCommandById and runOclifArgv now treat only ExitError with oclif.exit === 0 as silent exits. Other errors with oclif.exit === 0 now print output and set process.exitCode = 1, with unit, CLI, and provider-inference regression coverage updated.

Changes

Exit code fix for oclif error surfaces

Layer / File(s) Summary
oclif-runner catch-block logic fix
src/lib/cli/oclif-runner.ts
runOclifCommandById now treats only ExitError(0) as a silent exit 0 path, while other oclif.exit === 0 errors print a formatted or fallback message and set process.exitCode = 1; runOclifArgv now mirrors run, flush, and handle directly and applies the same error handling.
Unit test updates for surfaced failures
src/lib/cli/oclif-runner.test.ts
Updates the oclif mock surface, resets the expanded mocks and exit-code state, and asserts the new run, flush, handle, and ExitError(0) behaviors for both argv and command-id paths.
Hermetic exit-code regression coverage
test/exit-code-user-error-surfaces.test.ts, src/lib/onboard/machine/handlers/provider-inference.test.ts
Adds a hermetic CLI matrix with temporary HOME and PATH shims, plus a provider-inference failure case where router reconciliation throws a plain Error and the handler exits through exitProcess(1).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • cv
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing CLI exit behavior for errors that incorrectly rode on oclif.exit === 0.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/5974-exit-code-hygiene

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the fix/5974-exit-code-h... branch is 96%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/5974-exit-code-h... ffb91ad +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the fix/5974-exit-code-h... branch is 68%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/5974-exit-code-h... ffb91ad +/-
src/lib/actions...all/run-plan.ts 80%
src/lib/state/o...oard-session.ts 79%
src/lib/sandbox/config.ts 75%
src/lib/actions...dbox/rebuild.ts 74%
src/lib/state/sandbox.ts 72%
src/lib/onboard/preflight.ts 69%
src/lib/actions...licy-channel.ts 60%
src/lib/onboard...er-gpu-patch.ts 59%
src/lib/policy/index.ts 52%
src/lib/onboard.ts 20%

Updated June 30, 2026 07:39 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-1: Source-of-truth workaround for oclif.handle() exiting 0 on non-ExitError with oclif.exit===0; then add or justify PRA-T1.
Open items: 2 required · 2 warnings · 3 suggestions · 2 test follow-ups
Since last review: 2 prior items resolved · 1 still applies · 0 new items found

Action checklist

  • PRA-1 Fix: Source-of-truth workaround for oclif.handle() exiting 0 on non-ExitError with oclif.exit===0 in src/lib/cli/oclif-runner.ts:167
  • PRA-2 Fix: E2E regression test for [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 Instance 3: onboard dashboard-port exhaustion in test/exit-code-user-error-surfaces.test.ts:195
  • PRA-3 Resolve or justify: Instance 5 (Model Router Python preflight) justified by unit coverage — no E2E test in test/exit-code-user-error-surfaces.test.ts:42
  • PRA-4 Resolve or justify: Monolith growth: provider-inference.test.ts grew by 30 lines to 831 lines in src/lib/onboard/machine/handlers/provider-inference.test.ts:831
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Theoretical port conflict in parallel CI runs for dashboard-port exhaustion test
  • PRA-5 In-scope improvement: Security posture improved: exit-code reliability for automation security in src/lib/cli/oclif-runner.ts:1
  • PRA-6 In-scope improvement: Theoretical port conflict in parallel CI runs for dashboard-port exhaustion test in test/exit-code-user-error-surfaces.test.ts:1
  • PRA-7 In-scope improvement: Asymmetry between runOclifArgv and runOclifCommandById exit mechanisms documented in src/lib/cli/oclif-runner.ts:160

Findings index

ID Severity Category Location Required action
PRA-1 Required architecture src/lib/cli/oclif-runner.ts:167 Verify the removal condition comment at lines 183-189 is present and accurate: 'drop this guard once @oclif/core's handle() no longer exits 0 for a non-ExitError that carries oclif.exit === 0.' No code change needed if comment correctly states the upstream fix condition.
PRA-2 Required acceptance test/exit-code-user-error-surfaces.test.ts:195 Test added in this PR. Verify it passes in CI.
PRA-3 Resolve/justify acceptance test/exit-code-user-error-surfaces.test.ts:42 No E2E test needed. Unit coverage is sufficient. Traceability comment already present at lines 40-46. No further action needed.
PRA-4 Resolve/justify architecture src/lib/onboard/machine/handlers/provider-inference.test.ts:831 Extract the new #5974 test case ('exits non-zero when model router reconciliation throws', lines 662-683) into a focused test file (e.g., provider-inference-router.test.ts) or split provider-inference.test.ts by concern (provider selection vs inference setup vs router reconciliation). At minimum, offset the growth by removing any obsolete tests or consolidating duplicate setup.
PRA-5 Improvement security src/lib/cli/oclif-runner.ts:1 No action needed. Security posture improved by fixing silent-success-for-failure bug.
PRA-6 Improvement tests test/exit-code-user-error-surfaces.test.ts:1 Consider using SO_REUSEADDR or ephemeral port allocation with cleanup verification to avoid port conflicts in parallel CI runs. The current approach works but has a theoretical race.
PRA-7 Improvement docs src/lib/cli/oclif-runner.ts:160 Documentation already added. No further action needed.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-1 Required — Source-of-truth workaround for oclif.handle() exiting 0 on non-ExitError with oclif.exit===0

  • Location: src/lib/cli/oclif-runner.ts:167
  • Category: architecture
  • Problem: The catch block at lines 167-190 intercepts errors from runOclif() that carry oclif.exit===0 but are not genuine ExitError(0). It surfaces the message and forces process.exitCode=1 without delegating to handleOclif() (which would re-exit 0). This is a localized workaround for upstream oclif behavior.
  • Impact: If oclif core fixes handle() to not exit 0 for this case, the workaround becomes unnecessary and could be removed. Until then, it correctly prevents silent-success-for-failure on the native argv route.
  • Required action: Verify the removal condition comment at lines 183-189 is present and accurate: 'drop this guard once @oclif/core's handle() no longer exits 0 for a non-ExitError that carries oclif.exit === 0.' No code change needed if comment correctly states the upstream fix condition.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read src/lib/cli/oclif-runner.ts lines 167-190 for removal condition comment. Run vitest run src/lib/cli/oclif-runner.test.ts and verify the two native-route oclif.exit===0 tests pass. Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts and verify 'a native-route user error prints oclif's error and exits non-zero' passes.
  • Missing regression test: Already covered: unit tests in oclif-runner.test.ts (weird error, blank error cases) + E2E native-route test in exit-code-user-error-surfaces.test.ts
  • Done when: The required change is committed and verification passes: Read src/lib/cli/oclif-runner.ts lines 167-190 for removal condition comment. Run vitest run src/lib/cli/oclif-runner.test.ts and verify the two native-route oclif.exit===0 tests pass. Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts and verify 'a native-route user error prints oclif's error and exits non-zero' passes.
  • Evidence: src/lib/cli/oclif-runner.ts:167-190 catch block with removal condition comment; src/lib/cli/oclif-runner.test.ts:69-112 native-route oclif.exit===0 tests; test/exit-code-user-error-surfaces.test.ts:213-231 E2E native bogus-subcmd test

PRA-2 Required — E2E regression test for #5974 Instance 3: onboard dashboard-port exhaustion

  • Location: test/exit-code-user-error-surfaces.test.ts:195
  • Category: acceptance
  • Problem: New hermetic E2E test binds all dashboard ports 18789-18799, spawns `nemoclaw onboard --name port-test --no-gpu --non-interactive`, and asserts the canonical message 'All dashboard ports in range 18789-18799 are occupied' with non-zero exit.
  • Impact: Locks the end-to-end non-zero exit for this surface so it cannot silently regress to exit 0.
  • Required action: Test added in this PR. Verify it passes in CI.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts -t 'onboard dashboard-port exhaustion exits non-zero' and confirm it passes.
  • Missing regression test: Added: test/exit-code-user-error-surfaces.test.ts 'onboard dashboard-port exhaustion exits non-zero' describe block (lines 195-264)
  • Done when: The required change is committed and verification passes: Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts -t 'onboard dashboard-port exhaustion exits non-zero' and confirm it passes.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:195-264 dedicated describe block with port binding and spawnSync assertion
Review findings by urgency: 2 required fixes, 2 items to resolve/justify, 3 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-3 Resolve/justify — Instance 5 (Model Router Python preflight) justified by unit coverage — no E2E test

  • Location: test/exit-code-user-error-surfaces.test.ts:42
  • Category: acceptance
  • Problem: Instance 5 throws a plain Error (no oclif.exit) from prepareModelRouterVenv, caught in provider-inference.ts:424-428 → deps.exitProcess(1) → process.exit(1). This bypasses the oclif runner entirely, so the PR's oclif.exit===0 hardening does not affect it. Hermetic E2E would require faking full onboard flow plus a fake python3 reporting 3.14.4 - infeasible in current test architecture.
  • Impact: If the Model Router Python preflight path regresses to exit 0, automated onboarding scripts would not detect the failure. However, unit coverage is sufficient for release gating.
  • Recommended action: No E2E test needed. Unit coverage is sufficient. Traceability comment already present at lines 40-46. No further action needed.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read test/exit-code-user-error-surfaces.test.ts lines 40-46 for justification. Check src/lib/onboard/machine/handlers/provider-inference.ts:424-428 for the try/catch → exitProcess(1). Check src/lib/onboard/model-router-python.test.ts:143 for 'rejects a python whose version is at or above the exclusive ceiling' unit test.
  • Missing regression test: Covered by: model-router-python.test.ts unit test + provider-inference.ts try/catch exitProcess(1) + oclif-runner.test.ts thrown-error composition tests
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read test/exit-code-user-error-surfaces.test.ts lines 40-46 for justification. Check src/lib/onboard/machine/handlers/provider-inference.ts:424-428 for the try/catch → exitProcess(1). Check src/lib/onboard/model-router-python.test.ts:143 for 'rejects a python whose version is at or above the exclusive ceiling' unit test.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:40-46 justification comment; src/lib/onboard/machine/handlers/provider-inference.ts:424-428; src/lib/onboard/model-router-python.test.ts:143

PRA-4 Resolve/justify — Monolith growth: provider-inference.test.ts grew by 30 lines to 831 lines

  • Location: src/lib/onboard/machine/handlers/provider-inference.test.ts:831
  • Category: architecture
  • Problem: File handles provider selection, inference setup, resume paths, reconciliation, re-upsert, and Model Router Python preflight. The +30 line growth (from 801 baseline) was flagged as blocker severity in drift context.
  • Impact: Continued growth makes the test file harder to maintain, review, and navigate. Related test concerns are coupled in a single file.
  • Recommended action: Extract the new [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 test case ('exits non-zero when model router reconciliation throws', lines 662-683) into a focused test file (e.g., provider-inference-router.test.ts) or split provider-inference.test.ts by concern (provider selection vs inference setup vs router reconciliation). At minimum, offset the growth by removing any obsolete tests or consolidating duplicate setup.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check current line count of provider-inference.test.ts. Compare with the 801 baseline from drift context. Look for test consolidation opportunities or extraction candidates.
  • Missing regression test: N/A - architectural concern
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check current line count of provider-inference.test.ts. Compare with the 801 baseline from drift context. Look for test consolidation opportunities or extraction candidates.
  • Evidence: Drift context monolithDeltas shows +30 lines, severity blocker. File now 831 lines covering multiple handler behaviors.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-5 Improvement — Security posture improved: exit-code reliability for automation security

  • Location: src/lib/cli/oclif-runner.ts:1
  • Category: security
  • Problem: No secrets, credentials, or injection vectors in changed code. Exit-code reliability improved for CI/CD and watchdog scriptability. Error messages surfaced without leaking stack traces (ExitError 'EEXIT: 0' suppressed). Dependencies unchanged (@oclif/core).
  • Impact: Reliable non-zero exit codes on failure prevent silent failures in automated pipelines.
  • Suggested action: No action needed. Security posture improved by fixing silent-success-for-failure bug.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review diff for hardcoded secrets, command injection, path traversal, SSRF - none present.
  • Missing regression test: N/A
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Full diff review: no secrets, no new dependencies, no network calls, no credential handling, no shell execution in changed code paths

PRA-6 Improvement — Theoretical port conflict in parallel CI runs for dashboard-port exhaustion test

  • Location: test/exit-code-user-error-surfaces.test.ts:1
  • Category: tests
  • Problem: The E2E test binds real TCP ports (18789-18799) for the dashboard-port exhaustion test which could theoretically conflict if multiple test runs execute simultaneously in the same CI environment.
  • Impact: Flaky test failures in parallel CI if port range is already in use by another process.
  • Suggested action: Consider using SO_REUSEADDR or ephemeral port allocation with cleanup verification to avoid port conflicts in parallel CI runs. The current approach works but has a theoretical race.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check if the test uses SO_REUSEADDR or similar. The test creates net.Server instances without explicit reuseAddr option at lines 250-260.
  • Missing regression test: N/A - test infrastructure improvement
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:250-260 creates net.Server instances on fixed ports 18789-18799 without reuseAddr option

PRA-7 Improvement — Asymmetry between runOclifArgv and runOclifCommandById exit mechanisms documented

  • Location: src/lib/cli/oclif-runner.ts:160
  • Category: docs
  • Problem: runOclifArgv uses process.exitCode + return (to avoid re-entering handleOclif which would re-exit 0), while runOclifCommandById calls injected exit(). The mechanism asymmetry is now documented in code comments (lines 173-189).
  • Impact: Future maintainers understand why the mechanisms differ and won't incorrectly 'unify' them, which would break the native argv path.
  • Suggested action: Documentation already added. No further action needed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read src/lib/cli/oclif-runner.ts lines 173-189 for the mechanism asymmetry explanation.
  • Missing regression test: N/A - documentation improvement
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: src/lib/cli/oclif-runner.ts:173-189 comment block explaining mechanism asymmetry and removal condition
Simplification opportunities: 3 possible cuts, net -54 lines possible

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-1 delete (src/lib/cli/oclif-runner.ts:167): entire catch block at lines 167-190 including removal condition comment
    • Replacement: await handleOclif(error as Parameters<typeof handleOclif>[0]); (delegate all errors to oclif's handler)
    • Net: -24 lines
    • Safety boundary: Only remove after verifying upstream oclif has fixed handle() to not exit 0 for non-ExitError with oclif.exit===0. Run oclif core test suite or check changelog for fix.
  • PRA-4 shrink (src/lib/onboard/machine/handlers/provider-inference.test.ts:831): Extract router reconciliation test case (lines 662-683) and potentially other router-related tests
    • Replacement: New focused test file: src/lib/onboard/machine/handlers/provider-inference-router.test.ts
    • Net: -30 lines
    • Safety boundary: Ensure all extracted tests still pass and no test coverage is lost. Keep shared test utilities (createDeps, createSession, baseSelection) in a common test helper if needed.
  • PRA-6 native (test/exit-code-user-error-surfaces.test.ts:1): Fixed port range binding with manual server creation
    • Replacement: Use net.createServer({ allowHalfOpen: true }) with port 0 for ephemeral allocation, or set server.listen(0) and collect actual assigned ports, then bind the full range programmatically
    • Net: 0 lines
    • Safety boundary: Must still bind all 11 ports (18789-18799) to exhaust the dashboard port range exactly as the production code checks. Ephemeral allocation must be constrained to the same range.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Consider adding SO_REUSEADDR or ephemeral port allocation for dashboard-port exhaustion test to eliminate theoretical parallel CI port conflict (suggestion severity, not blocking). Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/cli/oclif-runner.ts. SATISFIED by new test/exit-code-user-error-surfaces.test.ts which spawns real bin/nemoclaw.js with fake shims (hermetic integration test).
  • PRA-T2 Theoretical port conflict in parallel CI runs for dashboard-port exhaustion test — Consider using SO_REUSEADDR or ephemeral port allocation with cleanup verification to avoid port conflicts in parallel CI runs. The current approach works but has a theoretical race.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Required — Source-of-truth workaround for oclif.handle() exiting 0 on non-ExitError with oclif.exit===0

  • Location: src/lib/cli/oclif-runner.ts:167
  • Category: architecture
  • Problem: The catch block at lines 167-190 intercepts errors from runOclif() that carry oclif.exit===0 but are not genuine ExitError(0). It surfaces the message and forces process.exitCode=1 without delegating to handleOclif() (which would re-exit 0). This is a localized workaround for upstream oclif behavior.
  • Impact: If oclif core fixes handle() to not exit 0 for this case, the workaround becomes unnecessary and could be removed. Until then, it correctly prevents silent-success-for-failure on the native argv route.
  • Required action: Verify the removal condition comment at lines 183-189 is present and accurate: 'drop this guard once @oclif/core's handle() no longer exits 0 for a non-ExitError that carries oclif.exit === 0.' No code change needed if comment correctly states the upstream fix condition.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read src/lib/cli/oclif-runner.ts lines 167-190 for removal condition comment. Run vitest run src/lib/cli/oclif-runner.test.ts and verify the two native-route oclif.exit===0 tests pass. Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts and verify 'a native-route user error prints oclif's error and exits non-zero' passes.
  • Missing regression test: Already covered: unit tests in oclif-runner.test.ts (weird error, blank error cases) + E2E native-route test in exit-code-user-error-surfaces.test.ts
  • Done when: The required change is committed and verification passes: Read src/lib/cli/oclif-runner.ts lines 167-190 for removal condition comment. Run vitest run src/lib/cli/oclif-runner.test.ts and verify the two native-route oclif.exit===0 tests pass. Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts and verify 'a native-route user error prints oclif's error and exits non-zero' passes.
  • Evidence: src/lib/cli/oclif-runner.ts:167-190 catch block with removal condition comment; src/lib/cli/oclif-runner.test.ts:69-112 native-route oclif.exit===0 tests; test/exit-code-user-error-surfaces.test.ts:213-231 E2E native bogus-subcmd test

PRA-2 Required — E2E regression test for #5974 Instance 3: onboard dashboard-port exhaustion

  • Location: test/exit-code-user-error-surfaces.test.ts:195
  • Category: acceptance
  • Problem: New hermetic E2E test binds all dashboard ports 18789-18799, spawns `nemoclaw onboard --name port-test --no-gpu --non-interactive`, and asserts the canonical message 'All dashboard ports in range 18789-18799 are occupied' with non-zero exit.
  • Impact: Locks the end-to-end non-zero exit for this surface so it cannot silently regress to exit 0.
  • Required action: Test added in this PR. Verify it passes in CI.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts -t 'onboard dashboard-port exhaustion exits non-zero' and confirm it passes.
  • Missing regression test: Added: test/exit-code-user-error-surfaces.test.ts 'onboard dashboard-port exhaustion exits non-zero' describe block (lines 195-264)
  • Done when: The required change is committed and verification passes: Run vitest run --project integration test/exit-code-user-error-surfaces.test.ts -t 'onboard dashboard-port exhaustion exits non-zero' and confirm it passes.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:195-264 dedicated describe block with port binding and spawnSync assertion

PRA-3 Resolve/justify — Instance 5 (Model Router Python preflight) justified by unit coverage — no E2E test

  • Location: test/exit-code-user-error-surfaces.test.ts:42
  • Category: acceptance
  • Problem: Instance 5 throws a plain Error (no oclif.exit) from prepareModelRouterVenv, caught in provider-inference.ts:424-428 → deps.exitProcess(1) → process.exit(1). This bypasses the oclif runner entirely, so the PR's oclif.exit===0 hardening does not affect it. Hermetic E2E would require faking full onboard flow plus a fake python3 reporting 3.14.4 - infeasible in current test architecture.
  • Impact: If the Model Router Python preflight path regresses to exit 0, automated onboarding scripts would not detect the failure. However, unit coverage is sufficient for release gating.
  • Recommended action: No E2E test needed. Unit coverage is sufficient. Traceability comment already present at lines 40-46. No further action needed.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read test/exit-code-user-error-surfaces.test.ts lines 40-46 for justification. Check src/lib/onboard/machine/handlers/provider-inference.ts:424-428 for the try/catch → exitProcess(1). Check src/lib/onboard/model-router-python.test.ts:143 for 'rejects a python whose version is at or above the exclusive ceiling' unit test.
  • Missing regression test: Covered by: model-router-python.test.ts unit test + provider-inference.ts try/catch exitProcess(1) + oclif-runner.test.ts thrown-error composition tests
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read test/exit-code-user-error-surfaces.test.ts lines 40-46 for justification. Check src/lib/onboard/machine/handlers/provider-inference.ts:424-428 for the try/catch → exitProcess(1). Check src/lib/onboard/model-router-python.test.ts:143 for 'rejects a python whose version is at or above the exclusive ceiling' unit test.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:40-46 justification comment; src/lib/onboard/machine/handlers/provider-inference.ts:424-428; src/lib/onboard/model-router-python.test.ts:143

PRA-4 Resolve/justify — Monolith growth: provider-inference.test.ts grew by 30 lines to 831 lines

  • Location: src/lib/onboard/machine/handlers/provider-inference.test.ts:831
  • Category: architecture
  • Problem: File handles provider selection, inference setup, resume paths, reconciliation, re-upsert, and Model Router Python preflight. The +30 line growth (from 801 baseline) was flagged as blocker severity in drift context.
  • Impact: Continued growth makes the test file harder to maintain, review, and navigate. Related test concerns are coupled in a single file.
  • Recommended action: Extract the new [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 test case ('exits non-zero when model router reconciliation throws', lines 662-683) into a focused test file (e.g., provider-inference-router.test.ts) or split provider-inference.test.ts by concern (provider selection vs inference setup vs router reconciliation). At minimum, offset the growth by removing any obsolete tests or consolidating duplicate setup.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Check current line count of provider-inference.test.ts. Compare with the 801 baseline from drift context. Look for test consolidation opportunities or extraction candidates.
  • Missing regression test: N/A - architectural concern
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Check current line count of provider-inference.test.ts. Compare with the 801 baseline from drift context. Look for test consolidation opportunities or extraction candidates.
  • Evidence: Drift context monolithDeltas shows +30 lines, severity blocker. File now 831 lines covering multiple handler behaviors.

PRA-5 Improvement — Security posture improved: exit-code reliability for automation security

  • Location: src/lib/cli/oclif-runner.ts:1
  • Category: security
  • Problem: No secrets, credentials, or injection vectors in changed code. Exit-code reliability improved for CI/CD and watchdog scriptability. Error messages surfaced without leaking stack traces (ExitError 'EEXIT: 0' suppressed). Dependencies unchanged (@oclif/core).
  • Impact: Reliable non-zero exit codes on failure prevent silent failures in automated pipelines.
  • Suggested action: No action needed. Security posture improved by fixing silent-success-for-failure bug.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Review diff for hardcoded secrets, command injection, path traversal, SSRF - none present.
  • Missing regression test: N/A
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Full diff review: no secrets, no new dependencies, no network calls, no credential handling, no shell execution in changed code paths

PRA-6 Improvement — Theoretical port conflict in parallel CI runs for dashboard-port exhaustion test

  • Location: test/exit-code-user-error-surfaces.test.ts:1
  • Category: tests
  • Problem: The E2E test binds real TCP ports (18789-18799) for the dashboard-port exhaustion test which could theoretically conflict if multiple test runs execute simultaneously in the same CI environment.
  • Impact: Flaky test failures in parallel CI if port range is already in use by another process.
  • Suggested action: Consider using SO_REUSEADDR or ephemeral port allocation with cleanup verification to avoid port conflicts in parallel CI runs. The current approach works but has a theoretical race.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Check if the test uses SO_REUSEADDR or similar. The test creates net.Server instances without explicit reuseAddr option at lines 250-260.
  • Missing regression test: N/A - test infrastructure improvement
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: test/exit-code-user-error-surfaces.test.ts:250-260 creates net.Server instances on fixed ports 18789-18799 without reuseAddr option

PRA-7 Improvement — Asymmetry between runOclifArgv and runOclifCommandById exit mechanisms documented

  • Location: src/lib/cli/oclif-runner.ts:160
  • Category: docs
  • Problem: runOclifArgv uses process.exitCode + return (to avoid re-entering handleOclif which would re-exit 0), while runOclifCommandById calls injected exit(). The mechanism asymmetry is now documented in code comments (lines 173-189).
  • Impact: Future maintainers understand why the mechanisms differ and won't incorrectly 'unify' them, which would break the native argv path.
  • Suggested action: Documentation already added. No further action needed.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read src/lib/cli/oclif-runner.ts lines 173-189 for the mechanism asymmetry explanation.
  • Missing regression test: N/A - documentation improvement
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: src/lib/cli/oclif-runner.ts:173-189 comment block explaining mechanism asymmetry and removal condition

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@yimoj

yimoj commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Real-CLI E2E transcript (reporter workflow)

Each of the five reporter surfaces was run through the worktree binary ./bin/nemoclaw.js (Node entry → dist/nemoclaw.js) with an isolated HOME, an empty on-disk registry, and fake openshell/docker shims so no live gateway is contacted. Every command prints its error text and returns a non-zero exit code:

$ ./bin/nemoclaw.js credentials reset
  Missing 1 required arg:
provider  OpenShell provider name
  => exit 2

$ ./bin/nemoclaw.js bug5974-sb skill install
  Sandbox 'bug5974-sb' does not exist.
  => exit 1

$ ./bin/nemoclaw.js bug5974-da-sb dcode --help
  Unknown command: bug5974-da-sb
  => exit 1

$ ./bin/nemoclaw.js bug5974-sb share mount /sandbox/bad-typo-path
  Sandbox 'bug5974-sb' does not exist.
  => exit 1

$ ./bin/nemoclaw.js bug5974-missing-sb upload some-file.txt
  Sandbox 'bug5974-missing-sb' does not exist.
  => exit 1

These are codified hermetically in test/exit-code-user-error-surfaces.test.ts, which spawns the same bin/nemoclaw.js and asserts non-zero exit + error text for each row.

Scope note on the code change vs. the matrix: the per-command surfaces already return non-zero on current main (release drift since the v0.0.68 report); the matrix is a regression guard for them. The behavioral fix in this PR is in src/lib/cli/oclif-runner.ts — the catch-all path where a command's run() throws an error merely carrying oclif.exit === 0. That path previously surfaced the message but reported success; it now exits non-zero (only a genuine oclif ExitError(0) stays exit 0). It is a defensive catch-all not directly reachable from a fixed user command, so it is covered by the unit tests in src/lib/cli/oclif-runner.test.ts rather than a CLI transcript. The onboard startup paths (dashboard-port exhaustion, Python preflight) were traced to propagate as thrown errors through onboard's try/finally with no swallowing catch, so they already exit non-zero and are intentionally untouched.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: onboard-negative-paths-vitest, sandbox-operations-vitest
Optional E2E: inference-routing-vitest, model-router-provider-routed-inference-vitest

Dispatch hint: onboard-negative-paths-vitest,sandbox-operations-vitest

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • onboard-negative-paths-vitest (medium): Required because the PR changes core CLI error/exit behavior and adds regression coverage for real-binary user-error surfaces, including an onboard negative startup failure. This PR-safe live Vitest job validates CLI exit/output behavior for onboard negative paths.
  • sandbox-operations-vitest (high): Required because the changed oclif runner is on the native sandbox command route and dispatcher command route. A real sandbox lifecycle smoke is needed to prove sandbox commands still parse, dispatch, fail, and recover correctly end-to-end.

Optional E2E

  • inference-routing-vitest (medium): Optional adjacent confidence: CLI runner exit/error handling can affect inference setup and route-validation commands, but no production inference-routing implementation changed.
  • model-router-provider-routed-inference-vitest (high): Optional adjacent confidence for the newly added provider-inference test around model-router reconciliation failure. Not required because only tests changed in the onboard provider-inference handler area.

New E2E recommendations

  • cli-user-error-exit-code-surfaces (medium): The PR adds a hermetic real-binary regression matrix in test/exit-code-user-error-surfaces.test.ts for broad CLI user-error exit-code surfaces. Existing E2E jobs cover important adjacent onboard and sandbox flows, but there is no clearly named existing E2E job dedicated to this exact cross-command exit-code matrix.
    • Suggested test: Promote or mirror the hermetic exit-code user-error matrix as a free-standing E2E Vitest scenario/job, for example cli-user-error-exit-codes-vitest, so future oclif/dispatcher changes can be selectively dispatched without running broader sandbox lifecycle suites.

Dispatch hint

  • Workflow: .github/workflows/e2e-vitest-scenarios.yaml
  • jobs input: onboard-negative-paths-vitest,sandbox-operations-vitest

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Vitest E2E Scenario Recommendation

Required Vitest E2E scenarios: ubuntu-repo-cloud-openclaw
Optional Vitest E2E scenarios: None

Dispatch required Vitest E2E scenarios:

  • gh workflow run e2e-vitest-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw

Workflow run

Full Vitest E2E advisor summary

Vitest E2E Scenario Advisor

Base: origin/main
Head: HEAD
Confidence: medium

Required Vitest E2E scenarios

  • ubuntu-repo-cloud-openclaw: The PR changes the shared oclif CLI runner and error/exit handling used by live scenario commands. The Ubuntu cloud OpenClaw scenario is the smallest live-supported typed scenario that exercises the repo CLI through install/onboarding and common sandbox/status/list/credentials surfaces on the primary Docker-backed path.
    • Dispatch: gh workflow run e2e-vitest-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw

Optional Vitest E2E scenarios

  • None.

Relevant changed files

  • src/lib/cli/oclif-runner.ts

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings

Merge posture: No blocking advisor findings
Primary next action: Add or justify PRA-T1 and any related test follow-ups.
Open items: 0 required · 0 warnings · 0 suggestions · 1 test follow-up
Since last review: 0 prior items resolved · 0 still apply · 0 new items found

Action checklist

  • PRA-T1 Add or justify test follow-up: Runtime validation
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — If maintainers want additional confidence for [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 instance 5, add or identify a focused test that a fresh onboard Model Router Python-preflight failure such as "No usable host Python interpreter found" exits non-zero through the top-level onboard command without requiring a live gateway.. The changed runner affects real process exit behavior, so runtime validation is appropriate. The PR adds both focused unit tests and a spawned bin/nemoclaw.js regression matrix, which covers the main changed behavior; an exact fresh Model Router Python-preflight spawn remains impractical without heavier environment setup.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@test/exit-code-user-error-surfaces.test.ts`:
- Line 108: The “skill install with no path” test case is currently passing on
sandbox resolution instead of exercising the missing-path validation in `skill
install`. Update the setup in `exit-code-user-error-surfaces.test.ts` so this
row uses a minimal sandbox entry or otherwise bypasses sandbox lookup, allowing
the `skill install` command path to reach its required-argument error. Keep the
assertion focused on the user-facing missing-path message surfaced by `skill
install`, not the sandbox “does not exist” 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: Enterprise

Run ID: 40a922df-d474-4786-bfd9-d678b1cb347e

📥 Commits

Reviewing files that changed from the base of the PR and between c6113be and 24bf025.

📒 Files selected for processing (3)
  • src/lib/cli/oclif-runner.test.ts
  • src/lib/cli/oclif-runner.ts
  • test/exit-code-user-error-surfaces.test.ts

Comment thread test/exit-code-user-error-surfaces.test.ts Outdated
The skill-install and unknown-action rows previously used an empty
registry, so they stopped at the dispatcher's "sandbox does not exist"
boundary instead of the issue's command-specific surfaces. Seed a single
registered sandbox so those rows resolve it and reach `skill install`'s
required-arg parser ("Missing 1 required arg: skillPath", exit 2) and the
dispatcher's unknown-action branch ("Unknown action: dcode", exit 1).
Keep the literal nonexistent-sandbox share/upload surfaces, and note that
the share-mount bad-remote-path diagnostic (#3414) is covered by existing
unit tests since it needs a live sandbox + host sshfs to reach.

Addresses PR Review Advisor PRA-1 and CodeRabbit feedback on PR #5986.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for PR Review Advisor items (922c9f2)

PRA-1 / PRA-T4 (regression matrix misses reported branches) — resolved. The matrix now seeds a single registered sandbox (bug5974-alpha) so the command-specific rows resolve it and reach their exact branches instead of the dispatcher's missing-sandbox boundary:

  • PRA-T1 / PRA-T8bug5974-alpha skill install (no path) now reaches skill install's required-arg parser → Missing 1 required arg: skillPath, exit 2. Asserted via the required arg substring.
  • PRA-T2bug5974-alpha dcode --help now reaches the dispatcher's unknown-action branch → Unknown action: dcode, exit 1. Asserted via the Unknown action: dcode substring.
  • PRA-T3share mount bad-remote-path (assertSandboxPathExistsOrExit, share mount: surface a clear error when the remote sandbox path does not exist #3414) needs a live sandbox and a host sshfs binary to reach, so it cannot run hermetically in a spawned-CLI matrix. That branch is already covered by src/lib/share-command.test.ts and test/share-command-remote-path.test.ts. The matrix keeps the reporter's literal nonexistent-sandbox share mount / upload surfaces (exit 1, "does not exist"). Documented inline in the test header.

PRA-T5 / PRA-T6 / PRA-T7 (acceptance clauses) — the five surfaces now each have a spawned-CLI row asserting non-zero exit + branch-specific text, and the diagnostic-preservation guarantee is held by the substring assertions plus the oclif-runner.test.ts message/blank-message unit tests. Note (already in the PR body): 4 of the 5 surfaces already exit non-zero on current main (release drift since the v0.0.68 report); the matrix is the regression guard, and the behavioral change is the oclif.exit === 0 catch-all in src/lib/cli/oclif-runner.ts.

All 5 matrix rows pass locally against the rebuilt bin/nemoclaw.js.

The spawned-CLI rows asserted only `code !== 0`, but a timeout or signal
kill leaves `spawnSync().status === null` (and the old `?? -1` fallback
was non-zero), so a killed process could satisfy the "non-zero exit"
claim without ever reaching the user-error branch. Assert the process
launched (`error` undefined), was not signal/timeout-killed
(`signal === null`), and returned a real positive exit code
(`status > 0`).

Addresses PR Review Advisor PRA-1 (re-run) on PR #5986.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for PR Review Advisor re-run PRA-1 (a1e6999)

PRA-1 (spawned CLI matrix can pass on timeout/signal termination) — resolved. The rows asserted only code !== 0, but a spawnSync timeout or signal kill leaves status === null (and the prior ?? -1 fallback was non-zero), so a killed process could have satisfied the non-zero-exit claim without reaching the user-error branch. The matrix now asserts the process actually launched and exited on its own:

  • expect(error).toBeUndefined() — the binary spawned (no ENOENT/spawn failure).
  • expect(signal).toBeNull() — not killed by a signal or the 30s timeout.
  • expect(status).toBeGreaterThan(0) — a real positive exit code (replaces not.toBe(0), which -1/null could spuriously satisfy).

PRA-T1PRA-T8 are the advisor's generic restatements of the same matrix concern; they are covered by the above plus the earlier 922c9f2 change (registered-sandbox rows reaching the exact skill install missing-path and Unknown action: dcode branches). All 5 rows pass locally against the rebuilt bin/nemoclaw.js.

The #5974 fix initially covered only runOclifCommandById. runOclifArgv —
the native route for `internal`, `sandbox`, and unknown-child commands —
called executeOclif, whose internal handle() runs Exit.exit(oclif.exit ??
1), so a non-ExitError error riding oclif.exit === 0 still exited 0 there.

Reimplement runOclifArgv as run → flush → handle by hand (what execute()
does) so oclif keeps owning command lookup, parsing, help, and
pretty-print, while we intercept the one case: a non-ExitError carrying
oclif.exit === 0 surfaces its message and forces exit 1 instead of
delegating to handle() (which would exit 0). Genuine ExitError(0)
(Command.exit(0)/--help) still delegates to handle() for the graceful
exit. Add runOclifArgv unit tests for the weird-error, blank-message, and
graceful-ExitError(0) cases.

Addresses PR Review Advisor PRA-2 on PR #5986.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for PR Review Advisor (Nemotron Ultra) items (aa41ca9)

  • PRA-2 (Required — runOclifArgv missing the fallback) — fixed. Confirmed real: executeOclif catches internally and oclif's handle() runs Exit.exit(err.oclif?.exit ?? 1), so a non-ExitError carrying oclif.exit === 0 exited 0 on the native internal/sandbox routes. A plain try/catch around executeOclif wouldn't help (handle() swallows + process.exits internally), so runOclifArgv is reimplemented as run → flush → handle by hand (exactly what execute() does) with the [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 guard inserted before delegating to handle(). oclif still owns command lookup, parsing, help, and pretty-print. Verified end-to-end: sandbox --help → 0, sandbox channels add (missing args) → 2, internal (not found) → 2, all unchanged.
  • PRA-1 (source-of-truth review) — resolved by PRA-2. Invalid state: a thrown command failure reported as exit 0. Source boundary: oclif's handle() mapping oclif.exit straight to the process exit. Constraint: we cannot change oclif, and must preserve its help/pretty-print. Fix: intercept only the non-ExitError + exit === 0 case at our call site; everything else delegates to handle(). Removal condition: drop the guard if oclif ever stops treating oclif.exit === 0 on arbitrary errors as a success.
  • PRA-3 / PRA-T5 (no unit test for runOclifArgv fallback) — fixed. Added runOclifArgv cases mocking run to reject with a non-ExitError oclif.exit === 0 (asserts process.exitCode === 1 + surfaced message), a blank-message variant (asserts fallback line + exit 1), and a genuine ExitError(0) (asserts silent delegation to handle()).
  • PRA-4 / PRA-T6 (E2E for argv blank-message) — justified. This is a defensive catch-all: triggering a real command's run() to throw a blank-message oclif.exit === 0 error from a spawned CLI isn't reproducible without a contrived fault-injection seam. The behavior is locked by the new runOclifArgv unit tests (the same shape the project uses for runOclifCommandById's [Ubuntu 22.04][CLI&UX][Recovery] nemoclaw status and list return empty output, exit 0 when container is stopped and gateway port is held #2666 coverage).
  • PRA-5 (verbatim error text not sanitized) — justified, out of scope. formatOclifError surfaces command-authored diagnostic messages, which is pre-existing behavior (unchanged since [Ubuntu 22.04][CLI&UX][Recovery] nemoclaw status and list return empty output, exit 0 when container is stopped and gateway port is held #2666); this PR neither adds nor widens any data path. Structured/JSON logging already routes through redactForLog. Adding a heuristic stderr scrubber to the generic error printer is a separate, broader change.
  • PRA-6 (Instances 3 & 5 onboard paths) — justified. findAvailableDashboardPort throws and onboard() wraps its body in try { … } finally { releaseOnboardLock() } with no swallowing catch, so the throw propagates to a non-zero exit; the Model Router Python preflight likewise throws. Both require a live gateway (and a GPU/Python 3.14 host for instance 5) to exercise end-to-end, so they can't join the hermetic spawn matrix; this is documented in the PR body's scope note.
  • PRA-7 (PATH-to-constant nit) — declined. runCli builds the env per spawn so each row gets a clean child; the single-use local keeps the helper self-contained. Non-blocking style preference.

The companion GPT-5.5 advisor on the same head returned merge_as_is (0 findings). All unit + integration tests pass locally.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/cli/oclif-runner.test.ts (1)

156-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the observable non-failure state for ExitError(0).

This test proves silence/delegation, but not that the runner avoided forcing process.exitCode = 1. Capture the exact error and assert the exit code stays non-failure.

Strengthen the graceful-exit assertion
-    runMock.mockRejectedValue(new ExitError("EEXIT: 0"));
+    const exitError = new ExitError("EEXIT: 0");
+    runMock.mockRejectedValue(exitError);
     const errorLine = vi.fn();
 
     await runOclifArgv(["sandbox", "list"], { rootDir: "/repo", error: errorLine });
 
     expect(errorLine).not.toHaveBeenCalled();
-    expect(handleMock).toHaveBeenCalled();
+    expect(process.exitCode).toBeUndefined();
+    expect(handleMock).toHaveBeenCalledWith(exitError);

As per path instructions, “Review tests for behavioral confidence rather than implementation lock-in.”

🤖 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 `@src/lib/cli/oclif-runner.test.ts` around lines 156 - 168, Strengthen the
graceful-exit test in oclif-runner.test.ts so it verifies the runner stayed in
the non-failure state for a native-route ExitError(0). In the runOclifArgv test
around the ExitError mock, keep asserting silence and delegation via errorLine
and handleMock, and also capture/assert that the exact ExitError instance does
not cause process.exitCode to be set to 1. Use the existing runOclifArgv,
runMock, and handleMock symbols to keep the check aligned with the runner
behavior rather than implementation details.

Source: Path instructions

🤖 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 `@src/lib/cli/oclif-runner.test.ts`:
- Around line 156-168: Strengthen the graceful-exit test in oclif-runner.test.ts
so it verifies the runner stayed in the non-failure state for a native-route
ExitError(0). In the runOclifArgv test around the ExitError mock, keep asserting
silence and delegation via errorLine and handleMock, and also capture/assert
that the exact ExitError instance does not cause process.exitCode to be set to
1. Use the existing runOclifArgv, runMock, and handleMock symbols to keep the
check aligned with the runner behavior rather than implementation details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 60e5999b-c310-4ef7-b3f1-b0395ad3d450

📥 Commits

Reviewing files that changed from the base of the PR and between a1e6999 and aa41ca9.

📒 Files selected for processing (2)
  • src/lib/cli/oclif-runner.test.ts
  • src/lib/cli/oclif-runner.ts

Strengthen the runOclifArgv graceful-exit case to prove the runner does
not force process.exitCode = 1 and that the exact ExitError instance is
delegated to oclif's handle() (which performs the graceful exit 0).

Addresses CodeRabbit nitpick on PR #5986.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output bug-fix PR fixes a bug or regression labels Jun 29, 2026
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

CodeRabbit nitpick resolution (e00ab90)

Nitpick — src/lib/cli/oclif-runner.test.ts 156-168 (🔵 Trivial): "Assert the observable non-failure state for ExitError(0)." — Already addressed.

The nitpick review was submitted at 12:17:48Z; commit e00ab90a7 ("test(cli): assert graceful ExitError(0) leaves exit code unset", 12:19:13Z) landed immediately after and implements exactly the requested behavioral assertion:

const exitError = new ExitError("EEXIT: 0");
runMock.mockRejectedValue(exitError);
// ...
expect(errorLine).not.toHaveBeenCalled();
expect(process.exitCode).toBeUndefined();          // runner does NOT force a failure code
expect(handleMock).toHaveBeenCalledWith(exitError); // delegates the graceful exit 0 to handle()

The subsequent @coderabbitai review re-run reported no new findings. Targeted suite green locally: vitest run src/lib/cli/oclif-runner.test.ts → 14/14 passed.

No code changes needed; flagging the item as resolved/stale.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@yimoj Thanks for the clarification — agreed, this nitpick is already addressed by e00ab90a7. The added assertion for ExitError(0) makes the non-failure behavior explicit, so no further change is needed here.

@NVIDIA NVIDIA deleted a comment from coderabbitai Bot Jun 30, 2026
The PR Review Advisor (PRA-1/PRA-2/PRA-T1-T4) asked for regression tests
locking the non-zero exit of onboard dashboard-port exhaustion (#5974
instance 3) and Model Router Python preflight (instance 5). Reaching
either surface through the real onboard flow hermetically requires
emulating a chain of host openshell capability gates (verified by local
probe: --version >= 0.0.44, then a request-body-credential-rewrite probe,
and more) that a bash shim cannot stand in for reliably.

Both surfaces already exit non-zero and, unlike the surfaces this PR
fixes, neither rode the oclif.exit === 0 catch-all: instance 3 exits via
explicit exitFn(1) (locked by onboard/dashboard-port.test.ts, asserting
exit code 1 + canonical message); instance 5 throws a plain Error with no
swallowing catch (locked by onboard/model-router-python.test.ts), whose
thrown-error -> non-zero exit composition is locked by
cli/oclif-runner.test.ts. Document this carve-out in the matrix header,
mirroring the existing #3414 deferral to unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for PR Review Advisor items PRA-1 / PRA-2 / PRA-T1–T4 (999eb13)

These four items all reduce to the same ask: lock the non-zero exit of #5974 instance 3 (onboard dashboard-port exhaustion) and instance 5 (Model Router Python preflight) in test/exit-code-user-error-surfaces.test.ts. Resolved by documenting the existing coverage and the hermeticity boundary in the matrix header, mirroring the file's existing #3414 carve-out.

Why not a spawn row in this matrix

I empirically probed the real onboard CLI under this file's hermetic harness (fake openshell/docker, isolated HOME, all 11 dashboard ports 18789–18799 bound, third-party notice pre-accepted). Reaching the port-exhaustion surface requires standing in for a chain of host gates, each of which the bash shim must emulate:

  1. openshell --version ≥ 0.0.44 (else onboard attempts a real network reinstall/download — not hermetic), then
  2. a request-body-credential-rewrite capability probe ([install] OpenShell binary is missing request-body-credential-rewrite support → Failed to reinstall openshell CLI), and more beyond that.

Emulating that chain in a shim is brittle and high-maintenance for a path that is already locked elsewhere. This is the same reasoning the matrix already applies to the #3414 share mount surface (deferred to unit tests).

Both surfaces already exit non-zero and never rode the bug this PR fixes

The #5974 fix hardens the oclif.exit === 0 catch-all in src/lib/cli/oclif-runner.ts. Neither instance 3 nor 5 ever flowed through that branch:

  • Instance 3 exits via an explicit exitFn(1) (process.exit(1)), so it never reaches oclif error handling at all. Locked by src/lib/onboard/dashboard-port.test.ts — "exits 1 with the canonical message when every port in the range is bound" asserts exitCode === 1 and the "All dashboard ports in range 18789-18799 are occupied" text.
  • Instance 5 throws a plain Error (No usable host Python interpreter found for Model Router. / … above supported ceiling …) from prepareModelRouterVenv (src/lib/onboard/model-router.ts:264), which propagates through onboard's try/finally with no swallowing catch. A plain Error carries no oclif.exit, so it always exits non-zero. Locked by src/lib/onboard/model-router-python.test.ts (the "above supported ceiling" reason and the thrown "No usable host Python interpreter found" message), and the thrown-error → non-zero process-exit composition is locked by src/lib/cli/oclif-runner.test.ts.

Verification (local, this head)

  • vitest run src/lib/onboard/dashboard-port.test.ts src/lib/onboard/model-router-python.test.ts40 passed
  • vitest run test/exit-code-user-error-surfaces.test.ts5 passed (header doc-only change; matrix unchanged)
  • Probe confirmed the real CLI exits status 1 at the port-exhaustion surface (and at every earlier gate), never 0.

No production code changed; the runner fix and its matrix remain as reviewed. PRA-T1–T4 are the test-follow-up framing of the same two items and are covered by the above.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: e2e-all
Optional E2E targets: None

Dispatch required E2E targets:

  • gh workflow run e2e.yaml --ref <pr-head-ref>

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: medium

Required E2E targets

  • e2e-all: The PR changes the shared oclif CLI runner used to dispatch NemoClaw commands, including exit-code/error handling for native routes. Because this command-entry surface can affect multiple live E2E targets and is not isolated to a single typed target, run the full E2E target fan-out.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref>

Optional E2E targets

  • None.

Relevant changed files

  • src/lib/cli/oclif-runner.ts

The GPT-5.5 PR Review Advisor (PRA-1) asked for spawned coverage of the
native oclif argv path (src/lib/cli/oclif-runner.ts): one native
parse/user-error route that exits non-zero, and one native help route
that stays a clean exit 0.

The user-error direction is already locked by the existing 'credentials
reset without a provider' row (oclif parse error, exit 2, native route).
Add the missing counterpart: a spawned 'credentials --help' case that
asserts a clean exit 0 through the real binary, so the #5974 hardening
cannot over-correct a genuine ExitError(0) graceful exit. This is the
real-CLI lock for the ExitError(0) unit test in
src/lib/cli/oclif-runner.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for GPT-5.5 advisor PRA-1 / PRA-T1–T3 — native oclif argv spawned coverage (fd26d4a)

PRA-1 (tests): "Add a small spawned CLI regression that uses the real bin/nemoclaw.js native route … one native parse/user-error route that must print oclif's formatted error and exit non-zero, and one native help route that must remain a clean exit 0." — Resolved.

Both directions of the native oclif argv route (src/lib/cli/oclif-runner.ts) are now locked by spawned real-binary cases in test/exit-code-user-error-surfaces.test.ts:

  • Native parse/user-error → non-zero — already covered by the existing credentials reset without a provider row: spawns bin/nemoclaw.js credentials reset, asserts oclif's formatted required arg text and a non-zero exit (exit 2). credentials is an oclif command, so this flows through the native argv route.
  • Native help → clean exit 0 — added: spawns bin/nemoclaw.js credentials --help, asserts the USAGE banner and status === 0. This guards against the [Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability #5974 hardening over-correcting a genuine ExitError(0) graceful exit, and is the spawned-CLI counterpart to the ExitError(0) unit test in src/lib/cli/oclif-runner.test.ts.

I verified the native route exit codes directly against the real binary:

invocation exit
nemoclaw --help 0
nemoclaw credentials --help 0
nemoclaw --version 0
nemoclaw credentials reset (missing arg) 2
nemoclaw totally-unknown-cmd 1

Verification (local, head fd26d4a)

  • vitest run test/exit-code-user-error-surfaces.test.ts6 passed (incl. new a native --help route stays a clean exit 0)
  • find-test-conditionals.ts / find-source-shape-tests.ts → file not flagged; biome clean

PRA-T1–T3 are the test-follow-up framing of this same native-route coverage and are covered by the above.

The prior 'credentials --help' case went through the by-id dispatcher
(runOclifCommandById), not the native argv route the advisor asked to
cover. dispatchCli routes a leading 'sandbox'/'internal' token straight
to runOclifArgv (src/lib/cli/oclif-runner.ts), so switch the spawned
coverage to that route and lock both directions:
  - 'sandbox bogus-subcmd' -> oclif 'command not found', exit non-zero
    (exit 2), exercising the native-path hardening this PR adds; and
  - 'sandbox --help' -> clean exit 0, a genuine ExitError(0) the
    hardening must not over-correct.
Both resolve at oclif command lookup before any gateway probe, so they
stay hermetic under the existing fakes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Correction for GPT-5.5 advisor PRA-1 — now targets the real native argv route (9663903)

The advisor correctly still flagged PRA-1 on fd26d4ae2: my first attempt used credentials --help, which dispatchCli routes through the by-id dispatcher (runOclifCommandById), not the native argv route the finding is about. Thanks — that was the right call.

dispatchCli sends a leading sandbox/internal token straight to runOclifArgv (src/lib/cli/oclif-runner.ts, the native path this PR hardens). The spawned coverage now uses that route and locks both directions:

  • Native parse/user-error → non-zero: nemoclaw sandbox bogus-subcmd → oclif's command sandbox:bogus-subcmd not found formatted error, exit 2.
  • Native help → clean exit 0: nemoclaw sandbox --helpUSAGE banner, exit 0 (a genuine ExitError(0) the hardening must not over-correct).

Both resolve at oclif's command lookup before any gateway probe, so they stay hermetic under the existing fakes.

Verification (local, head 9663903)

  • vitest run test/exit-code-user-error-surfaces.test.ts7 passed, incl. a native-route user error … exits non-zero and a native-route --help stays a clean exit 0
  • find-test-conditionals.ts / find-source-shape-tests.ts → file not flagged

This directly exercises runOclifArgv, so PRA-1 / PRA-T1–T4 (the native-route follow-ups) are now covered by real-binary regression tests rather than the by-id path.

yimoj and others added 2 commits June 30, 2026 05:56
The long it() title pushed the call past biome's line width; wrap it as
biome's formatter does so static-checks (Files were modified by hooks)
passes. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
Nemotron advisor PRA-1 asked for a regression test locking the non-zero
exit of onboard dashboard-port exhaustion (issue #5974 instance 3). Add a
hermetic spawn that binds the whole dashboard port range (18789-18799)
and drives the real onboard preflight to its fail-fast 'All dashboard
ports in range ... are occupied' exit, asserting a non-zero status.

The fake openshell shim reports a supported version and embeds the
request-body/websocket credential-rewrite capability markers the
installer greps for with 'strings', so preflight neither attempts a
network reinstall nor fails the capability gate before reaching the port
check. Verified hermetic (no download) and stable across repeated runs
(~1.5-3s). This surface exits via an explicit process.exit(1) and never
rode the oclif.exit === 0 catch-all, so the test simply locks the
end-to-end non-zero exit. Instance 5 (Model Router Python preflight) runs
only behind live gateway+provider+sandbox provisioning and stays covered
by its unit tests, as documented in the file header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Update for Nemotron advisor PRA-1 / PRA-2 — instance 3 now has a hermetic spawn test (900e83d)

Reworked the response after finding the onboard port-exhaustion path is hermetically reachable:

  • PRA-1 / instance 3 (onboard dashboard-port exhaustion) — now locked by a real spawn test. test/exit-code-user-error-surfaces.test.ts gained a dedicated describe that binds the whole dashboard port range (18789–18799) and drives the real onboard preflight to its fail-fast All dashboard ports in range 18789-18799 are occupied exit, asserting a non-zero status. The fake openshell shim reports a supported version and embeds the request-body-credential-rewrite / websocket-credential-rewrite markers the installer greps for with strings, so preflight neither attempts a network reinstall nor trips the capability gate. Verified hermetic (no download) and stable across repeated runs (~1.5–3s).

  • PRA-2 / instance 5 (Model Router Python preflight) — remains unit-tested, with rationale. reconcileModelRouter runs only deep in onboard, behind live gateway + provider + sandbox provisioning (onboard.ts:4426/5039), which cannot be faked hermetically in a spawn matrix the way the early port preflight can. It throws a plain Error (no oclif.exit, so unaffected by this PR) that propagates through onboard's try/finally with no swallowing catch — locked by src/lib/onboard/model-router-python.test.ts (the "above supported ceiling" reason and the "No usable host Python interpreter found" message), and the thrown-error → non-zero process-exit composition by src/lib/cli/oclif-runner.test.ts.

Verification (local, head 900e83d)

  • vitest run test/exit-code-user-error-surfaces.test.ts8 passed (incl. onboard dashboard-port exhaustion exits non-zero, stable across 2 runs)
  • tsc -p tsconfig.cli.json clean for the file; biome clean; find-test-conditionals / find-source-shape-tests / test-title-style not flagged

PRA-T1–T4 (the test follow-ups for instances 3 & 5) are covered by the above.

… condition (#5974)

Address Nemotron advisor improvement items on the #5974 hardening:
- PRA-4: explain why runOclifArgv forces a non-zero exit via
  process.exitCode + return (it mirrors oclif execute() and must not
  re-enter handle(), which would Exit.exit(0)) versus runOclifCommandById,
  which maps errors to codes by hand and never routes through handle().
- PRA-6 / PRA-1: state the removal condition — drop the guard once
  @oclif/core's handle() no longer exits 0 for a non-ExitError carrying
  oclif.exit === 0.
- PRA-5: cross-reference the runtime E2E counterpart
  (test/exit-code-user-error-surfaces.test.ts native --help case) from the
  mocked ExitError(0) unit test.

Comments only; no behavior or coverage change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@yimoj

yimoj commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Resolution for Nemotron advisor PRA-1 … PRA-6 (e935388)

The advisor's last pass reviewed 9663903bd (before the onboard port-exhaustion spawn landed in 900e83d13). Addressing every item against the current head e93538878:

Improvements — now implemented as code docs

  • PRA-4 (asymmetry) — Added a comment block at src/lib/cli/oclif-runner.ts explaining why the native runOclifArgv path forces a non-zero exit via process.exitCode + return (it mirrors oclif's execute() = run → flush → handle, so it must not re-enter handle(), which would Exit.exit(0) and undo the fix), versus runOclifCommandById, which maps errors to codes by hand (injected exit() for parse/ExitError, re-throw otherwise) and never routes through handle().
  • PRA-6 / PRA-1 (removal condition + architecture) — Documented the explicit removal condition inline: drop this guard once @oclif/core's handle() no longer exits 0 for a non-ExitError carrying oclif.exit === 0. That captures the invalid state (success reported for a real failure), the source boundary (oclif's handle()/Exit.exit), the source-fix constraint (we can't patch upstream oclif, hence the localized guard), and the removal trigger.
  • PRA-5 (test cross-ref) — Added a comment in the mocked ExitError(0) unit test (oclif-runner.test.ts) pointing to its runtime counterpart (test/exit-code-user-error-surfaces.test.tsa native-route --help stays a clean exit 0).

Acceptance items

  • PRA-2 (instance 3 — onboard dashboard-port exhaustion) — Resolved in 900e83d13 (which the advisor had not yet seen): a hermetic spawn binds ports 18789–18799 and drives the real onboard preflight to the canonical All dashboard ports in range 18789-18799 are occupied exit, asserting non-zero. See describe("onboard dashboard-port exhaustion exits non-zero (#5974)").
  • PRA-3 (instance 5 — Model Router Python preflight) — Justified: reconcileModelRouter runs only deep in onboard (onboard.ts:4426/5039), behind live gateway + provider + sandbox provisioning that the hermetic matrix cannot fake the way the early port preflight can. It throws a plain Error (no oclif.exit, unaffected by this PR) with no swallowing catch — locked by src/lib/onboard/model-router-python.test.ts plus src/lib/cli/oclif-runner.test.ts for the thrown-error → non-zero composition. Per the advisor's own acceptance clause, that unit coverage is sufficient for release gating for this surface.

Verification (local, head e935388)

  • vitest run src/lib/cli/oclif-runner.test.ts → 14 passed; vitest run test/exit-code-user-error-surfaces.test.ts → 8 passed
  • tsc -p tsconfig.cli.json clean; biome clean; comments only (no behavior/coverage change)

PRA-T1–T6 are the test-follow-up framing of the above and are covered.

@yimoj yimoj added the v0.0.73 label Jun 30, 2026
PRA-3: the Model Router Python preflight throw (#5974 instance 5) was
asserted only at the message level (model-router-python.test.ts); the
catch→exitProcess(1) composition in the routed provider/inference handler
was unlocked, and the E2E matrix comment misattributed it to
oclif-runner.test.ts (a path this plain-Error never traverses).

Add a provider-inference handler test asserting reconcileModelRouter's
throw is caught and converted to a non-zero exit, and correct the
instance-5 justification comment to reference the real chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
@wscurran wscurran added integration: dcode LangChain Deep Code integration behavior v0.0.74 and removed v0.0.73 labels Jul 2, 2026
@prekshivyas prekshivyas self-assigned this Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Vitest E2E Scenario Results — ❌ Some jobs failed

Run: 28612082640
Workflow ref: fix/5974-exit-code-hygiene
Requested scenarios: (selector rejected by workflow validation)
Requested jobs: (default — all default-enabled free-standing jobs; explicit-only jobs such as jetson-nvmap-gpu-vitest and sandbox-rlimits-connect-vitest are skipped unless selected)
Summary: 0 passed, 1 failed, 0 cancelled, 0 skipped

Job Result
generate-matrix ❌ failure

Failed jobs: generate-matrix. Check run artifacts for logs.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Vitest E2E Target Results — ✅ All requested jobs passed

Run: 28612470340
Workflow ref: main
Requested targets: (default — all supported)
Requested jobs: onboard-negative-paths,sandbox-operations
Summary: 2 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
onboard-negative-paths ✅ success
sandbox-operations ✅ success

@prekshivyas prekshivyas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Nemotron PRA-1 and PRA-2 are verification-only (comment present at oclif-runner.ts:183-189, port-exhaustion test passes in CI). Standard advisor is merge_as_is. E2E green on onboard-negative-paths and sandbox-operations.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Vitest E2E Scenario Results — ✅ All requested jobs passed

Run: 28636444972
Workflow ref: fix/5974-exit-code-hygiene
Requested scenarios: (default — all supported)
Requested jobs: onboard-negative-paths-vitest,sandbox-operations-vitest
Summary: 2 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
onboard-negative-paths-vitest ✅ success
sandbox-operations-vitest ✅ success

@cv
cv merged commit 56b9ef5 into main Jul 3, 2026
189 of 190 checks passed
@cv
cv deleted the fix/5974-exit-code-hygiene branch July 3, 2026 07:34
@ericksoa ericksoa mentioned this pull request Jul 4, 2026
21 tasks
ericksoa added a commit that referenced this pull request Jul 4, 2026
<!-- markdownlint-disable MD041 -->
## Summary
This PR prepares the user-facing documentation for v0.0.74 before the
release plan is frozen.
It expands the release notes across the 56-commit train and closes
durable documentation gaps found during the pre-tag commit scan.

## Changes
- Expand the `v0.0.74` release notes to cover OpenShell 0.0.72, managed
MCP, progressive tool disclosure, LangChain Deep Agents Code,
onboarding, local inference, messaging, recovery, and contributor
workflows.
- Correct the `destroy` contract for retained per-name volumes,
gateway-unreachable `--force` cleanup, managed MCP ownership, and
same-name recovery.
- Document separate remediation for an unreachable container DNS
resolver versus one that answers with `NXDOMAIN` or `REFUSED`.
- Document the Windows on Arm N1X automatic Ollama safeguard and its
remaining large-model limitations.
- State that messaging conflicts abort rebuild before backup or
deletion, leaving the original sandbox intact.
- Link the agent-runnable value benchmark from the contributor task
index.
- Synchronize generated agent command variants.
- Validate with `npm run docs:sync-agent-variants` and `npm run docs`;
Fern completed with 0 errors and 2 existing warnings.
- Source summary:
- [#6020](#6020) and
[#5876](#5876) ->
`docs/about/release-notes.mdx`: Consolidate the OpenShell 0.0.72 policy
boundary and managed MCP lifecycle.
- [#6251](#6251) and
[#5989](#5989) ->
`docs/about/release-notes.mdx`: Summarize progressive tool disclosure
and sandbox-first inference controls.
- [#6232](#6232),
[#6082](#6082),
[#6219](#6219),
[#6214](#6214),
[#6215](#6215),
[#6230](#6230), and
[#6260](#6260) ->
`docs/about/release-notes.mdx`: Summarize the experimental LangChain
Deep Agents Code status, secret, version, rebuild, snapshot, and MCP
boundaries.
- [#6166](#6166),
[#6254](#6254),
[#6265](#6265),
[#6164](#6164), and
[#6017](#6017) ->
`docs/about/release-notes.mdx`: Summarize BuildKit prebuild, validated
image reuse, bounded readiness, and preflight improvements.
- [#6150](#6150) ->
`docs/about/release-notes.mdx` and `docs/reference/troubleshooting.mdx`:
Separate unreachable-resolver remediation from reachable-but-rejected
DNS responses.
- [#6234](#6234) ->
`docs/about/release-notes.mdx`,
`docs/inference/use-local-inference.mdx`, and
`docs/get-started/windows-preparation.mdx`: Document N1X automatic 9B
selection and the remaining explicit-large-model boundary.
- [#6129](#6129),
[#5987](#5987),
[#5955](#5955), and
[#6220](#6220) ->
`docs/about/release-notes.mdx`,
`docs/manage-sandboxes/messaging-channels.mdx`,
`docs/reference/commands.mdx`, and
`docs/reference/commands-nemohermes.mdx`: Document messaging policy
persistence, status, and the pre-destructive conflict check.
- [#5963](#5963),
[#6050](#6050),
[#6094](#6094),
[#6238](#6238),
[#5988](#5988),
[#6235](#6235),
[#6181](#6181), and
[#5986](#5986) ->
`docs/about/release-notes.mdx`, `docs/reference/commands.mdx`, and
`docs/reference/commands-nemohermes.mdx`: Summarize day-two recovery and
clarify retained-volume and local-only destroy semantics.
- [#6200](#6200),
[#6248](#6248),
[#6168](#6168),
[#6270](#6270), and
[#5649](#5649) ->
`docs/about/release-notes.mdx` and `CONTRIBUTING.md`: Summarize
contributor setup and verification improvements and expose the advisory
value benchmark.

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [x] Doc only (includes code sample changes)

## Quality Gates
<!-- Check exactly one tests line and one docs line. Check other lines
when applicable. Add every requested justification or approval
reference. -->
- [ ] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [x] Tests not applicable — justification: documentation-only release
preparation; generated-variant synchronization and the Fern docs build
validate the changed pages and routes.
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification
<!-- Check each applicable item only when supported by the requested
evidence. Run targeted tests once per relevant change set and rerun
after later edits or hook autofixes that can affect the tested behavior.
Do not rerun hook-covered checks. -->
- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification: tests
are not applicable to this documentation-only change; `npm run docs`
validates the source and generated routes.
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only)
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
<!-- DCO sign-off is required in this PR description, and every commit
must appear as Verified in GitHub. Run: git config user.name && git
config user.email -->
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Expanded setup guidance for Windows on Arm devices with safer default
local model selection.
* Clarified local inference and sandbox messaging behavior, including
conflict checks before rebuilds and safer recovery steps.
* Updated destroy/rebuild/reference docs with more detailed warnings,
failure handling, and volume-retention guidance.
* Improved troubleshooting instructions for Docker DNS issues with
clearer paths for unreachable vs. blocked resolvers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
…exit === 0 (NVIDIA#5974) (NVIDIA#5986)

## Summary

Several `nemoclaw` user-error and unknown-command surfaces returned exit
`0` even though they printed correct error text, which breaks `$?`-based
scriptability (a watchdog or CI step wrapping the CLI could not detect
the failure). This hardens the last structural exit-`0` hole in the
oclif runner and locks the reported surfaces with a regression matrix.

## Related Issue

Closes NVIDIA#5974

## Changes

- `src/lib/cli/oclif-runner.ts`: an error that merely happens to carry
`oclif.exit === 0` (propagated out of a command's `run()`, not oclif's
own graceful `ExitError(0)`) is now treated as a genuine failure — its
message is surfaced **and** `process.exitCode` is set to `1`. Only a
real `ExitError(0)` (e.g. `Command.exit(0)` / `--help`, whose synthetic
`EEXIT: 0` message must stay silent) keeps exit `0`. The blank-message
fallback line from NVIDIA#2666 is preserved.
- `test/exit-code-user-error-surfaces.test.ts`: new hermetic regression
matrix that runs the real `nemoclaw` binary against fake
`openshell`/`docker` shims with an isolated `HOME`. A single sandbox is
seeded so the command-specific rows resolve it and reach their exact
branches: `credentials reset` (missing provider) and `<existing> skill
install` (missing path) both hit the missing-required-arg parser,
`<existing> dcode --help` hits the unknown-action branch, and `share
mount` / `upload` against a nonexistent sandbox hit the literal reporter
surfaces.
- `src/lib/cli/oclif-runner.test.ts`: updated the two NVIDIA#2666 unit tests
to assert the corrected non-zero exit while keeping the surfaced-message
intent.

Scope note: the per-command surfaces were re-tested on current `main`
and already return non-zero (release drift since the v0.0.68 report);
the matrix guards them against future regression, while the runner
change closes the remaining catch-all path. The onboard startup paths
(dashboard-port exhaustion, Python preflight) were verified to propagate
as thrown errors through onboard's `try/finally` (no swallowing catch)
and already exit non-zero, so they are left untouched. The `share mount`
bad-remote-path diagnostic (NVIDIA#3414) needs a live sandbox + host `sshfs`
to reach, so it stays covered by `src/lib/share-command.test.ts` /
`test/share-command-remote-path.test.ts` rather than the hermetic spawn
matrix.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)

## Quality Gates

- [x] Tests added or updated for changed behavior
- [x] Docs not applicable — justification: no user-facing behavior or
flag changes; only exit codes are corrected to be non-zero on
already-documented error messages.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: CLI runner change is
additive (only converts a wrongly-successful failure into a non-zero
exit) and preserves legitimate graceful `ExitError(0)`; covered by unit
+ integration tests.

## Verification

- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Git hooks passed during commit and push, or `npx prek run
--from-ref main --to-ref HEAD` passes
- [x] Targeted tests pass for changed behavior
- [x] No secrets, API keys, or credentials committed

### Reporter-workflow E2E (real worktree CLI)

Ran each reporter surface through the worktree binary
`./bin/nemoclaw.js` (Node entry → `dist/nemoclaw.js`) with an isolated
`HOME`, a registry seeded with one sandbox (`bug5974-alpha`), and fake
`openshell`/`docker` shims so no live gateway is contacted. Every
command prints its error text and exits **non-zero**:

```
$ node ./bin/nemoclaw.js credentials reset
  Missing 1 required arg:
provider  OpenShell provider name
  => exit 2

$ node ./bin/nemoclaw.js bug5974-alpha skill install      # existing sandbox, missing path
  Missing 1 required arg:
skillPath  Skill directory or direct path to SKILL.md
  => exit 2

$ node ./bin/nemoclaw.js bug5974-alpha dcode --help        # existing sandbox, unknown action
  Unknown action: dcode
  Valid actions: agent, agents, channels, ... skill, snapshot, status, upload
  => exit 1

$ node ./bin/nemoclaw.js bug5974-missing-sb share mount /sandbox/bad-typo-path
  Sandbox 'bug5974-missing-sb' does not exist.
  => exit 1

$ node ./bin/nemoclaw.js bug5974-missing-sb upload some-file.txt
  Sandbox 'bug5974-missing-sb' does not exist.
  => exit 1
```

This exact reporter workflow is codified hermetically in
`test/exit-code-user-error-surfaces.test.ts`, which spawns the same
`bin/nemoclaw.js` and asserts non-zero exit + branch-specific error text
for each row.

The behavioral fix itself lives in `src/lib/cli/oclif-runner.ts` (the
`oclif.exit === 0` catch-all that previously surfaced a message but
reported success). That path is a defensive catch-all not directly
reachable from a fixed user command, so it is covered by unit tests in
`src/lib/cli/oclif-runner.test.ts` rather than a CLI transcript.

Other tests run locally:
- `vitest run --project cli src/lib/cli/oclif-runner.test.ts` (11
passed)
- `vitest run --project integration
test/exit-code-user-error-surfaces.test.ts` (5 passed)
- `vitest run --project integration
test/repro-2666-silent-list-status.test.ts` (9 passed, no regression)
- `npm run typecheck:cli`, `npm run typecheck`, biome lint + format,
test title/size/overlap checks, `prek run --from-ref main --to-ref HEAD`
— all pass.

---
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Hardened CLI handling so errors that merely *carry* an exit code of
`0` are treated as failures: they now surface a non-empty error message
and exit with a non-zero status.
* Preserved quiet behavior for genuine successful `ExitError(0)` exits.
* **Tests**
* Updated CLI runner tests to reflect the updated oclif mocking and the
success-vs-failure exit/output distinctions.
* Added end-to-end regression coverage for CLI error surfaces and
dashboard port exhaustion.
* Added a `NVIDIA#5974` provider inference regression test to confirm failure
propagation and logging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary
This PR prepares the user-facing documentation for v0.0.74 before the
release plan is frozen.
It expands the release notes across the 56-commit train and closes
durable documentation gaps found during the pre-tag commit scan.

## Changes
- Expand the `v0.0.74` release notes to cover OpenShell 0.0.72, managed
MCP, progressive tool disclosure, LangChain Deep Agents Code,
onboarding, local inference, messaging, recovery, and contributor
workflows.
- Correct the `destroy` contract for retained per-name volumes,
gateway-unreachable `--force` cleanup, managed MCP ownership, and
same-name recovery.
- Document separate remediation for an unreachable container DNS
resolver versus one that answers with `NXDOMAIN` or `REFUSED`.
- Document the Windows on Arm N1X automatic Ollama safeguard and its
remaining large-model limitations.
- State that messaging conflicts abort rebuild before backup or
deletion, leaving the original sandbox intact.
- Link the agent-runnable value benchmark from the contributor task
index.
- Synchronize generated agent command variants.
- Validate with `npm run docs:sync-agent-variants` and `npm run docs`;
Fern completed with 0 errors and 2 existing warnings.
- Source summary:
- [NVIDIA#6020](NVIDIA#6020) and
[NVIDIA#5876](NVIDIA#5876) ->
`docs/about/release-notes.mdx`: Consolidate the OpenShell 0.0.72 policy
boundary and managed MCP lifecycle.
- [NVIDIA#6251](NVIDIA#6251) and
[NVIDIA#5989](NVIDIA#5989) ->
`docs/about/release-notes.mdx`: Summarize progressive tool disclosure
and sandbox-first inference controls.
- [NVIDIA#6232](NVIDIA#6232),
[NVIDIA#6082](NVIDIA#6082),
[NVIDIA#6219](NVIDIA#6219),
[NVIDIA#6214](NVIDIA#6214),
[NVIDIA#6215](NVIDIA#6215),
[NVIDIA#6230](NVIDIA#6230), and
[NVIDIA#6260](NVIDIA#6260) ->
`docs/about/release-notes.mdx`: Summarize the experimental LangChain
Deep Agents Code status, secret, version, rebuild, snapshot, and MCP
boundaries.
- [NVIDIA#6166](NVIDIA#6166),
[NVIDIA#6254](NVIDIA#6254),
[NVIDIA#6265](NVIDIA#6265),
[NVIDIA#6164](NVIDIA#6164), and
[NVIDIA#6017](NVIDIA#6017) ->
`docs/about/release-notes.mdx`: Summarize BuildKit prebuild, validated
image reuse, bounded readiness, and preflight improvements.
- [NVIDIA#6150](NVIDIA#6150) ->
`docs/about/release-notes.mdx` and `docs/reference/troubleshooting.mdx`:
Separate unreachable-resolver remediation from reachable-but-rejected
DNS responses.
- [NVIDIA#6234](NVIDIA#6234) ->
`docs/about/release-notes.mdx`,
`docs/inference/use-local-inference.mdx`, and
`docs/get-started/windows-preparation.mdx`: Document N1X automatic 9B
selection and the remaining explicit-large-model boundary.
- [NVIDIA#6129](NVIDIA#6129),
[NVIDIA#5987](NVIDIA#5987),
[NVIDIA#5955](NVIDIA#5955), and
[NVIDIA#6220](NVIDIA#6220) ->
`docs/about/release-notes.mdx`,
`docs/manage-sandboxes/messaging-channels.mdx`,
`docs/reference/commands.mdx`, and
`docs/reference/commands-nemohermes.mdx`: Document messaging policy
persistence, status, and the pre-destructive conflict check.
- [NVIDIA#5963](NVIDIA#5963),
[NVIDIA#6050](NVIDIA#6050),
[NVIDIA#6094](NVIDIA#6094),
[NVIDIA#6238](NVIDIA#6238),
[NVIDIA#5988](NVIDIA#5988),
[NVIDIA#6235](NVIDIA#6235),
[NVIDIA#6181](NVIDIA#6181), and
[NVIDIA#5986](NVIDIA#5986) ->
`docs/about/release-notes.mdx`, `docs/reference/commands.mdx`, and
`docs/reference/commands-nemohermes.mdx`: Summarize day-two recovery and
clarify retained-volume and local-only destroy semantics.
- [NVIDIA#6200](NVIDIA#6200),
[NVIDIA#6248](NVIDIA#6248),
[NVIDIA#6168](NVIDIA#6168),
[NVIDIA#6270](NVIDIA#6270), and
[NVIDIA#5649](NVIDIA#5649) ->
`docs/about/release-notes.mdx` and `CONTRIBUTING.md`: Summarize
contributor setup and verification improvements and expose the advisory
value benchmark.

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [x] Doc only (includes code sample changes)

## Quality Gates
<!-- Check exactly one tests line and one docs line. Check other lines
when applicable. Add every requested justification or approval
reference. -->
- [ ] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [x] Tests not applicable — justification: documentation-only release
preparation; generated-variant synchronization and the Fern docs build
validate the changed pages and routes.
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification
<!-- Check each applicable item only when supported by the requested
evidence. Run targeted tests once per relevant change set and rerun
after later edits or hook autofixes that can affect the tested behavior.
Do not rerun hook-covered checks. -->
- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification: tests
are not applicable to this documentation-only change; `npm run docs`
validates the source and generated routes.
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only)
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
<!-- DCO sign-off is required in this PR description, and every commit
must appear as Verified in GitHub. Run: git config user.name && git
config user.email -->
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Expanded setup guidance for Windows on Arm devices with safer default
local model selection.
* Clarified local inference and sandbox messaging behavior, including
conflict checks before rebuilds and safer recovery steps.
* Updated destroy/rebuild/reference docs with more detailed warnings,
failure handling, and volume-retention guidance.
* Improved troubleshooting instructions for Docker DNS issues with
clearer paths for unreachable vs. blocked resolvers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output bug-fix PR fixes a bug or regression integration: dcode LangChain Deep Code integration behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Linux][CLI&UX] nemoclaw returns exit 0 on 5 user-error / startup-failure surfaces — breaks $? scriptability

4 participants