Skip to content

Commit 692d1fa

Browse files
authored
fix(connect): exit zero when probe succeeds without platform evidence (#9282)
<!-- markdownlint-disable MD041 --> ## Summary On macOS, `nemoclaw <name> connect --probe-only` completed the gateway probe and any dashboard-forward recovery, then exited 1 because the launch-readiness evidence store requires a Linux per-user runtime authority (`/run/user/<uid>`). The permanent platform gap turned every successful probe into a failure, so a scripted health check could not tell a healthy sandbox from a real outage. After this change, a successful probe and recovery on such a platform prints a note that evidence is unavailable and exits 0. ## Related Issue Closes #9278 ## Changes - `src/lib/actions/sandbox/connect.ts`: when publication reports `evidence-failed` and the readiness decision carries `authorityUnsupported` (thrown only for non-Linux platforms in `src/lib/state/launch-readiness-lease.ts`), print `Note: launch-readiness evidence is unavailable on this platform; the next launch runs the complete preflight.` and return with exit 0. A publication failure on a platform that supports evidence keeps `Probe failed: ...` and exit 1. Fence failures, validation failures, and unsafe-epoch exits are unchanged. - `src/lib/actions/sandbox/connect-flow.test.ts`: the macOS-shaped case now asserts recovery completes, the note prints, and the command resolves with no exit call. The sibling cases for Linux publication failure and validation failure still assert exit 1. - `test/cli/connect-recovery.test.ts`, `test/cli/connect-terminal-agent.test.ts`, `test/sandbox-connect-inference/auto-pair-approval.test.ts`: probe-only now expects exit 0 on every platform; the note substring still appears only on darwin. - `docs/reference/commands.mdx`, `docs/manage-sandboxes/recover-rebuild-sandboxes.mdx`: state the new macOS behavior and scope the nonzero publication-failure exits to Linux. ### Design record PR #8951 (#8942 launch-readiness leases) recorded the previous contract: macOS probe-only "completes recovery and probes, then returns nonzero because authoritative evidence is unavailable." This PR narrows that decision for the permanent platform gap only, per the QA expectation in #9278: the probe's product operation succeeded, `launch` runs the complete preflight without evidence on these platforms, and no consumer relies on the macOS nonzero exit. Verified consumers: internal probe-only callers (`start.ts`, `hermes-cron-restore-recovery.ts`) pass `requireLaunchReadinessPublication: false` and return before the changed branch; the E2E lease producer (`test/e2e/live/launch-agent-turn.ts`) requires exit 0; managed-cloud checks treat probe-only nonzero as failure. Linux infrastructure-producer strictness is untouched: a broken `/run/user/<uid>` classifies as `missing`, not `unsupported`, and still exits nonzero. ## Type of Change - [x] Code change with doc updates ## Quality Gates - [x] Tests added or updated for changed behavior - [x] Docs updated for user-facing behavior changes - [x] 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: requested through this PR's maintainer review (sandbox connect path) ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `docs-updated` - Evidence: `docs/reference/commands.mdx`, `docs/manage-sandboxes/recover-rebuild-sandboxes.mdx`; review verified the changed sentences against `src/lib/actions/sandbox/connect.ts` and the controlled-word list, and its one accuracy suggestion (scoping `commands.mdx:1277` to Linux) is applied in this commit - Agent: Claude Code <!-- docs-review-head-sha: 8b5f742 --> <!-- docs-review-agents-blob-sha: e30afb2 --> ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` 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: `npx vitest run src/lib/actions/sandbox/connect-flow.test.ts` 35/35 passed; `npx vitest run test/cli/connect-recovery.test.ts test/cli/connect-terminal-agent.test.ts` 6/6 passed; `npx vitest run test/sandbox-connect-inference/auto-pair-approval.test.ts` 9/9 passed; `npm run typecheck:cli` clean - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [x] `npm run docs` builds without warnings (doc changes only) — exits 0; the 2 remaining warnings (fern auth, theme contrast) exist on `main` before this change ### macOS verification plan The Linux CI lanes exercise the unchanged behavior. The darwin branch is covered by the unit test (platform-independent readiness-decision shape) and will be verified on an Apple Silicon Mac against the exact #9278 repro (`connect --probe-only` on a healthy sandbox, then the forward-recovery variant); evidence will be posted as a PR comment. --- Signed-off-by: Dongni Yang <dongniy@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * macOS `connect --probe-only` now completes recovery successfully when launch-readiness evidence is unavailable. * Probe-only checks consistently return exit code `0` when core checks pass. * macOS evidence limitations are clearly reported as informational notes rather than failures. * Linux readiness and publication failures continue to return nonzero results with appropriate diagnostics. * **Documentation** * Clarified platform-specific probe-only behavior and subsequent launch checks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Dongni Yang <dongniy@nvidia.com>
1 parent 8b9a28a commit 692d1fa

7 files changed

Lines changed: 35 additions & 20 deletions

File tree

docs/manage-sandboxes/recover-rebuild-sandboxes.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ For missing, unsafe, malformed, expired, mismatched, changed, or unhealthy evide
173173
Ordinary launch continues only when NemoClaw proves that no old authority or evidence can exist, or durably rotates the runtime epoch.
174174
If an old epoch might exist and cannot be durably rotated, `launch` and `connect --probe-only` stop before complete preflight or recovery.
175175
Their redacted guidance asks you to repair the current user's secure OS runtime authority and NemoClaw state permissions, then retry.
176-
If NemoClaw securely proves that both the authority and receipt are absent but cannot create new authority, ordinary `launch` can run the complete preflight without optimization; `connect --probe-only` exits nonzero because it could not publish evidence.
176+
If NemoClaw securely proves that both the authority and receipt are absent but cannot create new authority, ordinary `launch` can run the complete preflight without optimization; on Linux, `connect --probe-only` exits nonzero because it could not publish evidence.
177177
If that preflight succeeds before the lease expires, replacement evidence keeps the original start and expiry time.
178178
After expiry, a successful complete preflight starts a new 24-hour lease only when publication succeeds.
179179

@@ -190,8 +190,9 @@ Lease acceptance and publication are currently Linux-only and require a secure,
190190
It never uses caller-provided environment variables to select this authority.
191191

192192
On macOS, `launch` runs the complete preflight every time and does not publish a launch-readiness lease.
193-
`connect --probe-only` also runs the complete preflight, including recovery and probes, but exits nonzero because it cannot publish authoritative launch-readiness evidence.
194-
The publication-failure diagnostic is redacted and does not print filesystem paths or environment values.
193+
`connect --probe-only` also runs the complete preflight, including recovery and probes.
194+
After a successful probe and recovery, it prints a note that launch-readiness evidence is unavailable on this platform and exits zero.
195+
On Linux, the publication-failure diagnostic is redacted and does not print filesystem paths or environment values.
195196

196197
Infrastructure must run `connect --probe-only` as the same final numeric user that later runs `launch`.
197198
Run it after the final durable home and state volume is mounted and after policy and network provisioning is complete.

docs/reference/commands.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,16 +1273,18 @@ It validates a usable lease and exits without duplicate recovery.
12731273
Otherwise, it fences prior evidence, waits for the sandbox, verifies or repairs its in-sandbox agent process and host-side forwards, and publishes evidence only after every probe succeeds.
12741274
It rechecks the sandbox on its recorded OpenShell gateway after the readiness wait and never restarts the shared host gateway.
12751275
If an old runtime epoch might exist and cannot be durably rotated, the command exits nonzero before complete preflight or recovery and gives redacted repair guidance.
1276-
A securely absent runtime authority and receipt let ordinary `launch` run the complete preflight without optimization if new authority creation fails, but `connect --probe-only` still exits nonzero because it could not publish launch-readiness evidence.
1277-
A runtime failure and a failure to publish evidence for an otherwise healthy runtime also exit nonzero with different diagnostics.
1276+
A securely absent runtime authority and receipt let ordinary `launch` run the complete preflight without optimization if new authority creation fails, but on Linux `connect --probe-only` still exits nonzero because it could not publish launch-readiness evidence.
1277+
A runtime failure and, on Linux, a failure to publish evidence for an otherwise healthy runtime also exit nonzero with different diagnostics.
12781278

12791279
Infrastructure must run the command as the same final numeric user that later runs `launch`.
12801280
Run it only after the final durable home and state volume is mounted and after policy and network provisioning is complete.
12811281
On Linux, that user also needs a secure, independently writable OS per-user runtime authority under `/run/user/<numeric-uid>`.
12821282
Do not redirect this authority with caller environment variables.
12831283
Do not use a graphical or login-session identifier as the deployment ordering boundary.
1284-
On macOS, `connect --probe-only` runs the complete preflight, including recovery and probes, but exits nonzero because it cannot publish authoritative launch-readiness evidence.
1285-
The publication-failure diagnostic is redacted and does not print filesystem paths or environment values.
1284+
On macOS, `connect --probe-only` runs the complete preflight, including recovery and probes.
1285+
After a successful probe and recovery, it prints a note that launch-readiness evidence is unavailable on this platform and exits zero.
1286+
The next `launch` runs the complete preflight.
1287+
On Linux, the publication-failure diagnostic is redacted and does not print filesystem paths or environment values.
12861288
Run it for health checks and scripted readiness probes; users continue to run only `$$nemoclaw launch <name>`.
12871289

12881290
Use [`$$nemoclaw launch <name>`](#$$nemoclaw-launch-name) when you want launch-readiness validation, an automatic fallback that runs the complete preflight, and then the agent instead of a sandbox shell.

src/lib/actions/sandbox/connect-flow.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ describe("connectSandbox flow", () => {
635635
);
636636
});
637637

638-
it("probe-only completes macOS recovery before reporting unavailable evidence (#8942)", async () => {
638+
it("probe-only completes macOS recovery and exits zero when evidence is unavailable (#9278)", async () => {
639639
const harness = createConnectHarness({
640640
readinessDecision: {
641641
kind: "fallback",
@@ -650,16 +650,16 @@ describe("connectSandbox flow", () => {
650650
readinessPublicationResult: { kind: "evidence-failed" },
651651
});
652652

653-
await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
654-
"process.exit(1)",
655-
);
653+
await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();
656654

657655
expect(harness.checkAndRecoverSpy).toHaveBeenCalledOnce();
658656
expect(harness.ensureLiveSandboxSpy).toHaveBeenCalled();
659657
expect(harness.publishLaunchReadinessSpy).toHaveBeenCalledOnce();
660-
expect(harness.errorSpy).toHaveBeenCalledWith(
661-
" Probe failed: complete probe and recovery succeeded, but launch-readiness evidence is unavailable on this platform.",
658+
expect(exitSpy).not.toHaveBeenCalled();
659+
expect(harness.logSpy).toHaveBeenCalledWith(
660+
" Note: launch-readiness evidence is unavailable on this platform; the next launch runs the complete preflight.",
662661
);
662+
expect(harness.errorSpy.mock.calls.flat().join("\n")).not.toContain("Probe failed");
663663
});
664664

665665
it("lets a public lifecycle command continue after recovery when evidence publication is unavailable (#8942)", async () => {

src/lib/actions/sandbox/connect.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,10 +1332,19 @@ export async function connectSandbox(
13321332
}
13331333
if (publication.kind === "evidence-failed") {
13341334
if (!requireLaunchReadinessPublication) return;
1335+
// A platform without a per-user runtime authority (macOS) can never
1336+
// store launch-readiness evidence. The probe and recovery still
1337+
// succeeded, and `launch` runs the complete preflight without the
1338+
// evidence, so a permanent platform gap must not turn a successful
1339+
// probe into a nonzero exit (#9278).
1340+
if (readiness.kind === "fallback" && readiness.authorityUnsupported === true) {
1341+
console.log(
1342+
" Note: launch-readiness evidence is unavailable on this platform; the next launch runs the complete preflight.",
1343+
);
1344+
return;
1345+
}
13351346
console.error(
1336-
readiness.kind === "fallback" && readiness.authorityUnsupported === true
1337-
? " Probe failed: complete probe and recovery succeeded, but launch-readiness evidence is unavailable on this platform."
1338-
: " Probe failed: complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published.",
1347+
" Probe failed: complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published.",
13391348
);
13401349
process.exit(1);
13411350
}

test/cli/connect-recovery.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ const launchReadinessObservationStubLines = [
4747
"fi",
4848
];
4949

50-
const expectedProbeOnlyExitCode = process.platform === "darwin" ? 1 : 0;
5150
const PLATFORM_EVIDENCE_UNAVAILABLE = "launch-readiness evidence is unavailable on this platform";
5251

5352
function expectProbeOnlyPublicationOutcome(result: { code: number; out: string }): void {
54-
expect(result.code, result.out).toBe(expectedProbeOnlyExitCode);
53+
// Evidence unavailability on macOS is a note, not a failure (#9278).
54+
expect(result.code, result.out).toBe(0);
5555
expect(result.out.includes(PLATFORM_EVIDENCE_UNAVAILABLE)).toBe(process.platform === "darwin");
56+
expect(result.out.includes("Probe failed")).toBe(false);
5657
}
5758

5859
function writeGatewayControlDockerStub(

test/cli/connect-terminal-agent.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ describe("CLI dispatch for terminal agents", () => {
7070
PATH: `${localBin}:${process.env.PATH || ""}`,
7171
});
7272

73-
expect(r.code).toBe(process.platform === "darwin" ? 1 : 0);
73+
// Evidence unavailability on macOS is a note, not a failure (#9278).
74+
expect(r.code).toBe(0);
7475
expect(r.out.includes(PLATFORM_EVIDENCE_UNAVAILABLE)).toBe(process.platform === "darwin");
7576
expect(r.out).toContain("terminal smoke checks passed");
7677
const calls = fs.readFileSync(markerFile, "utf8").trim().split("\n").filter(Boolean);

test/sandbox-connect-inference/auto-pair-approval.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
setupFixture,
2020
} from "./helpers";
2121

22-
const expectedProbeOnlyExitCode = process.platform === "darwin" ? 1 : 0;
22+
// Evidence unavailability on macOS is a note, not a failure (#9278).
23+
const expectedProbeOnlyExitCode = 0;
2324

2425
function findApprovalExec(state: {
2526
sandboxExecCalls: string[][];

0 commit comments

Comments
 (0)