Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ For missing, unsafe, malformed, expired, mismatched, changed, or unhealthy evide
Ordinary launch continues only when NemoClaw proves that no old authority or evidence can exist, or durably rotates the runtime epoch.
If an old epoch might exist and cannot be durably rotated, `launch` and `connect --probe-only` stop before complete preflight or recovery.
Their redacted guidance asks you to repair the current user's secure OS runtime authority and NemoClaw state permissions, then retry.
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.
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.
If that preflight succeeds before the lease expires, replacement evidence keeps the original start and expiry time.
After expiry, a successful complete preflight starts a new 24-hour lease only when publication succeeds.

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

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

Infrastructure must run `connect --probe-only` as the same final numeric user that later runs `launch`.
Run it after the final durable home and state volume is mounted and after policy and network provisioning is complete.
Expand Down
10 changes: 6 additions & 4 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1273,16 +1273,18 @@ It validates a usable lease and exits without duplicate recovery.
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.
It rechecks the sandbox on its recorded OpenShell gateway after the readiness wait and never restarts the shared host gateway.
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.
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.
A runtime failure and a failure to publish evidence for an otherwise healthy runtime also exit nonzero with different diagnostics.
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.
A runtime failure and, on Linux, a failure to publish evidence for an otherwise healthy runtime also exit nonzero with different diagnostics.

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

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.
Expand Down
12 changes: 6 additions & 6 deletions src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ describe("connectSandbox flow", () => {
);
});

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

await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow(
"process.exit(1)",
);
await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

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

it("lets a public lifecycle command continue after recovery when evidence publication is unavailable (#8942)", async () => {
Expand Down
15 changes: 12 additions & 3 deletions src/lib/actions/sandbox/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1332,10 +1332,19 @@ export async function connectSandbox(
}
if (publication.kind === "evidence-failed") {
if (!requireLaunchReadinessPublication) return;
// A platform without a per-user runtime authority (macOS) can never
// store launch-readiness evidence. The probe and recovery still
// succeeded, and `launch` runs the complete preflight without the
// evidence, so a permanent platform gap must not turn a successful
// probe into a nonzero exit (#9278).
if (readiness.kind === "fallback" && readiness.authorityUnsupported === true) {
console.log(
" Note: launch-readiness evidence is unavailable on this platform; the next launch runs the complete preflight.",
);
return;
}
console.error(
readiness.kind === "fallback" && readiness.authorityUnsupported === true
? " Probe failed: complete probe and recovery succeeded, but launch-readiness evidence is unavailable on this platform."
: " Probe failed: complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published.",
" Probe failed: complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published.",
);
process.exit(1);
}
Expand Down
5 changes: 3 additions & 2 deletions test/cli/connect-recovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ const launchReadinessObservationStubLines = [
"fi",
];

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

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

function writeGatewayControlDockerStub(
Expand Down
3 changes: 2 additions & 1 deletion test/cli/connect-terminal-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ describe("CLI dispatch for terminal agents", () => {
PATH: `${localBin}:${process.env.PATH || ""}`,
});

expect(r.code).toBe(process.platform === "darwin" ? 1 : 0);
// Evidence unavailability on macOS is a note, not a failure (#9278).
expect(r.code).toBe(0);
expect(r.out.includes(PLATFORM_EVIDENCE_UNAVAILABLE)).toBe(process.platform === "darwin");
expect(r.out).toContain("terminal smoke checks passed");
const calls = fs.readFileSync(markerFile, "utf8").trim().split("\n").filter(Boolean);
Expand Down
3 changes: 2 additions & 1 deletion test/sandbox-connect-inference/auto-pair-approval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
setupFixture,
} from "./helpers";

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

function findApprovalExec(state: {
sandboxExecCalls: string[][];
Expand Down
Loading