Skip to content
Merged
32 changes: 31 additions & 1 deletion docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,37 @@ NemoClaw records onboarding progress so interrupted runs can continue.
Use `--resume` to continue a resumable onboarding session with the provider, model, sandbox name, agent, observability choice, custom Dockerfile path, read-only host-mount declarations, and any explicitly selected serving-profile provenance recorded by the original run.
For a profile-backed session, resume requires the same catalog, preset, and recipe digests and exits before effects if the installed definition changed.
Omit `--profile` to reuse that recorded selection, or pass the same profile explicitly; use `--fresh` to adopt a changed catalog definition.
Legacy sessions without a profile-provenance record continue to resume normally, but cannot acquire a new `--profile` selection during resume.
Sessions without a serving-profile provenance record can resume when their checkpoint uses schema 4, but they cannot acquire a new `--profile` selection during resume.

Checkpoint schema 4 records whether onboarding uses the default profile or the portable experimental profile.
For the portable profile, it also records the current user's canonical home reported by the operating system, that home's exact `.config` directory, the runtime root, rootless Podman endpoint path, and runtime ownership.
It does not record ambient Docker or Podman runtime selector values.
The runtime authority record contains no credentials.
A plain `--resume` restores the recorded profile.
You can also run `$$nemoclaw onboard --experimental-profile portable --resume` when the recorded profile is portable.
NemoClaw rejects an explicit profile that conflicts with the checkpoint before it changes portable configuration, activates the user-scoped Podman socket, or changes gateway and sandbox resources.

Portable resume derives `DOCKER_HOST`, `CONTAINERS_CONF`, and `NETAVARK_FW` again while it holds the onboarding lock.
It ignores ambient Docker and Podman runtime selectors during that derivation.
NemoClaw scopes the derived values to onboarding and restores the process environment after success or failure.
It verifies the current user, canonical roots, socket path and ownership, Podman identity and version, and required configuration before a resumed onboarding step changes resources.
Resume stops before writes or activation if an existing socket or configuration path is a symlink, has the wrong owner, or has an unsafe type or mode.
NemoClaw can create missing descendants beneath a validated current-user root and reconcile content drift in its own portable configuration files.
A missing user-scoped socket after a host reboot can be activated and verified at the recorded path.
A new socket inode or a supported Podman upgrade does not invalidate the checkpoint.
Portable onboarding always uses the `.config` directory beneath the canonical home reported by the operating system.
`HOME` and `XDG_CONFIG_HOME` never select or override this authority.
NemoClaw ignores ambient `XDG_CONFIG_HOME` during onboarding and restores its exact prior presence and value afterward.
Resume rejects a checkpoint that records another configuration root.
It also rejects stored authority or filesystem ownership drift without falling back to Docker.

<Warning title="Checkpoint Resume Compatibility">
An active onboarding session with checkpoint schema 1, 2, or 3 cannot resume because those schemas did not record the default or portable profile authority.
NemoClaw preserves the older session and exits before portable configuration, socket activation, or resource changes.
Run `$$nemoclaw onboard --fresh` to discard the active session and start fresh onboarding.
If you intend to use the portable experimental profile, run `$$nemoclaw onboard --experimental-profile portable --fresh`.
This compatibility restriction does not prevent NemoClaw from reading a completed older session during status inspection.
</Warning>

Before the configuration review, NemoClaw records the sandbox name and the selected provider and model as an incomplete choice.
If onboarding stops at the review prompt, an interactive `--resume` run shows the prompt again.
Expand Down
35 changes: 34 additions & 1 deletion docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ When NemoClaw prints the state move, run the exact commands it prints:
3. Move the selected state directory into the archive as `gateway-state`.
4. Run the printed onboarding command only after the stop, the archive, and the move succeed.
Standard onboarding prints `$$nemoclaw onboard --resume`.
The portable experimental profile prints its required fresh-onboarding command because it does not support resume.
The portable experimental profile prints its required fresh-onboarding command for this gateway-state recovery.

The archive remains beside the selected state directory and retains the previous gateway records and credentials.
Keep it owner-only until onboarding completes and every required sandbox and provider registration is restored.
Expand Down Expand Up @@ -1176,6 +1176,33 @@ This is only useful if the original failure was transient, for example a network
$$nemoclaw onboard --resume
```

For a checkpoint schema 4 portable session, the plain command restores the portable profile from the checkpoint.
You can also state the matching profile explicitly:

```bash
$$nemoclaw onboard --experimental-profile portable --resume
```

Portable resume does not trust ambient Docker or Podman runtime selectors.
It derives and verifies the recorded current-user rootless Podman authority before it continues onboarding.
If NemoClaw reports unsafe ownership, type, or mode, correct that filesystem condition and retry.
Portable onboarding always uses the `.config` directory beneath the canonical home reported by the operating system; changing `HOME` or `XDG_CONFIG_HOME` does not select another location.
For a recorded alternate configuration root or other user ID, home, runtime root, endpoint, runtime kind, or ownership drift, do not edit the checkpoint; run fresh onboarding.

If NemoClaw reports that an active checkpoint uses schema 1, 2, or 3, the older checkpoint did not record enough profile and runtime authority for resume.
NemoClaw preserves the session and exits before portable configuration, socket activation, or resource changes.
Discard that active session and start fresh onboarding:

```bash
$$nemoclaw onboard --fresh
```

If you intend to use the portable experimental profile, select it again for fresh onboarding:

```bash
$$nemoclaw onboard --experimental-profile portable --fresh
```

<AgentOnly variant="openclaw">

OpenClaw resume does not repeat completed non-secret sandbox, web search, messaging, or resource choices.
Expand Down Expand Up @@ -3295,6 +3322,12 @@ Then rerun portable onboarding:
$$nemoclaw onboard --experimental-profile portable
```

If the failed run has a checkpoint schema 4 resumable session, resume it without exporting Docker or Podman runtime selectors:

```bash
$$nemoclaw onboard --resume
```

Continue only when onboarding no longer reports that the Podman service or OpenShell Podman host gateway is unreachable.

<AgentOnly variant="hermes">
Expand Down
2 changes: 2 additions & 0 deletions src/lib/actions/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { loadServingCatalog } from "../inference/serving/catalog-loader";
import type { GooglechatTunnelRuntimeDeps } from "../messaging/channels/googlechat/hooks/tunnel-runtime";
import { type OnboardCommandOptions, runOnboardCommand } from "../onboard/command";
import { type OnboardFlags, readAgentRegistryNames } from "../onboard/command-support";
import { resolveOnboardResumeIntent } from "../onboard/session-bootstrap";
import { loadServingProfileResumeSession } from "../onboard/sandbox-registration";
import type { OnboardOptions } from "../onboard/types";

Expand Down Expand Up @@ -32,6 +33,7 @@ function buildOnboardCommandDeps(flags: OnboardFlags, runtimeDeps: OnboardAction
listAgents: () => [...readAgentRegistryNames()],
loadServingCatalog,
loadSession: loadServingProfileResumeSession,
resolveResumeIntent: resolveOnboardResumeIntent,
log: console.log,
error: console.error,
exit: (code: number) => process.exit(code),
Expand Down
21 changes: 20 additions & 1 deletion src/lib/adapters/podman/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

import { describe, expect, it, vi } from "vitest";

import { createPodmanContainerEngine, type PodmanSocketAuthority } from "./index";
import {
createPodmanContainerEngine,
localPodmanEnvironment,
type PodmanSocketAuthority,
} from "./index";

const AUTHORITY = {
directoryChain: [],
Expand All @@ -15,6 +19,21 @@ const AUTHORITY = {
} as const satisfies PodmanSocketAuthority;

describe("Podman container engine command adapter", () => {
it("removes ambient remote and Docker TLS selectors from local Podman commands (#9035)", () => {
const source = {
CONTAINER_HOST: "ssh://attacker.test",
CONTAINER_CONNECTION: "attacker",
CONTAINER_SSHKEY: "/tmp/attacker-key",
DOCKER_TLS: "1",
DOCKER_TLS_VERIFY: "1",
DOCKER_CERT_PATH: "/tmp/attacker-certs",
KEEP: "value",
};

expect(localPodmanEnvironment(source)).toEqual({ KEEP: "value" });
expect(source.DOCKER_TLS_VERIFY).toBe("1");
});

it("pins the exact socket around each operation-scoped command", () => {
const assertAuthority = vi.fn();
const capture = vi.fn(() => ({ status: 0, stdout: "ok", stderr: "" }));
Expand Down
3 changes: 3 additions & 0 deletions src/lib/adapters/podman/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export function localPodmanEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEn
delete local.CONTAINER_CONNECTION;
delete local.CONTAINER_HOST;
delete local.CONTAINER_SSHKEY;
delete local.DOCKER_TLS;
delete local.DOCKER_TLS_VERIFY;
delete local.DOCKER_CERT_PATH;
return local;
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib/build-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,14 @@ describe("printSandboxCreateRecoveryHints", () => {
expect(out).toContain("onboard --resume");
});

it("prints the portable-profile recovery command when the portable env is set", () => {
it("prints checkpoint resume recovery when the portable env is set (#9035)", () => {
const prev = process.env.NEMOCLAW_EXPERIMENTAL_PROFILE;
process.env.NEMOCLAW_EXPERIMENTAL_PROFILE = "portable";
try {
printSandboxCreateRecoveryHints("");
const out = stderr();
expect(out).toContain("onboard --experimental-profile portable");
expect(out).not.toContain("--resume");
expect(out).toContain("onboard --resume");
expect(out).not.toContain("onboard --experimental-profile portable");
expect(out).not.toContain("Or: nemoclaw onboard");
} finally {
prev === undefined
Expand Down
4 changes: 2 additions & 2 deletions src/lib/build-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fs from "node:fs";
import path from "node:path";
import { CLI_NAME } from "./cli/branding";
import { isPortableExperimentalProfile } from "./onboard/experimental/portable-profile";
import { noteOnboardResumeHintShown, onboardRecoveryCommand } from "./onboard/resume-hint";
import { noteOnboardResumeHintShown, onboardResumeRecoveryCommand } from "./onboard/resume-hint";

import { classifySandboxCreateFailure, planSandboxCreateRecovery } from "./validation";

Expand Down Expand Up @@ -109,7 +109,7 @@ export function printSandboxCreateRecoveryHints(
// the generic incomplete-exit backstop (#6003).
noteOnboardResumeHintShown();
const portable = isPortableExperimentalProfile();
const recoveryCommand = onboardRecoveryCommand(portable);
const recoveryCommand = onboardResumeRecoveryCommand();
const failure = classifySandboxCreateFailure(output);
if (failure.kind === "image_upload_container_missing") {
const { arm64ImageRefWorkaround } = planSandboxCreateRecovery(failure, { platform, arch });
Expand Down
Loading
Loading