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
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,44 @@ $ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
$ nemoclaw onboard
```

### Previous onboarding session failed

If a previous `nemoclaw onboard` attempt fails partway through (for example, a provider or inference-setup step reporting an error), NemoClaw records the failure in `~/.nemoclaw/onboard-session.json`.

When you re-run the installer, it detects the failed session and does not silently retry it.
Silent retry would loop on the same failure if your original choice, such as an unreachable provider, was the cause.

- In an interactive terminal, the installer prompts whether to resume the failed session or start fresh.
Press `R` (or Enter) to retry the same session, or `f` to discard it and make fresh choices.
- In non-interactive mode (piped `curl | bash` with `NEMOCLAW_NON_INTERACTIVE=1`, CI, scripts), the installer refuses and exits with a non-zero status so a scripted re-run cannot loop.
You must opt in to one of two paths explicitly:

Start over with new choices (discards the recorded session and provider/model selection):

```console
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- --fresh
```

Or equivalently, via env var.
The variable must be set on the `bash` side of the pipe, not on `curl`, since only the right-hand process inherits it:

```console
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_FRESH=1 bash
```

Retry the same session without re-prompting.
This is only useful if the original failure was transient, for example a network blip or a stopped Docker daemon, and not a wrong provider choice:

```console
$ nemoclaw onboard --resume
```

As a last resort, you can also delete the session file directly and re-run the installer:

```console
$ rm ~/.nemoclaw/onboard-session.json
```

Comment thread
laitingsheng marked this conversation as resolved.
## Runtime

### Reconnect after a host reboot
Expand Down
38 changes: 38 additions & 0 deletions docs/reference/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,44 @@ $ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
$ nemoclaw onboard
```

### Previous onboarding session failed

If a previous `nemoclaw onboard` attempt fails partway through (for example, a provider or inference-setup step reporting an error), NemoClaw records the failure in `~/.nemoclaw/onboard-session.json`.

When you re-run the installer, it detects the failed session and does not silently retry it.
Silent retry would loop on the same failure if your original choice, such as an unreachable provider, was the cause.

- In an interactive terminal, the installer prompts whether to resume the failed session or start fresh.
Press `R` (or Enter) to retry the same session, or `f` to discard it and make fresh choices.
- In non-interactive mode (piped `curl | bash` with `NEMOCLAW_NON_INTERACTIVE=1`, CI, scripts), the installer refuses and exits with a non-zero status so a scripted re-run cannot loop.
You must opt in to one of two paths explicitly:

Start over with new choices (discards the recorded session and provider/model selection):

```console
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- --fresh
```

Or equivalently, via env var.
The variable must be set on the `bash` side of the pipe, not on `curl`, since only the right-hand process inherits it:

```console
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_FRESH=1 bash
```

Retry the same session without re-prompting.
This is only useful if the original failure was transient, for example a network blip or a stopped Docker daemon, and not a wrong provider choice:

```console
$ nemoclaw onboard --resume
```

As a last resort, you can also delete the session file directly and re-run the installer:

```console
$ rm ~/.nemoclaw/onboard-session.json
```

## Runtime

### Reconnect after a host reboot
Expand Down
2 changes: 2 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ bootstrap_usage() {
printf " Options:\n"
printf " --non-interactive Skip prompts (uses env vars / defaults)\n"
printf " --yes-i-accept-third-party-software Accept the third-party software notice in non-interactive mode\n"
printf " --fresh Discard any failed/interrupted onboarding session and start over\n"
printf " --version, -v Print installer version and exit\n"
printf " --help, -h Show this help message and exit\n\n"
printf " Environment:\n"
printf " NEMOCLAW_INSTALL_TAG Git ref to install (default: latest release)\n"
printf " NEMOCLAW_NON_INTERACTIVE=1 Same as --non-interactive\n"
printf " NEMOCLAW_FRESH=1 Same as --fresh\n"
printf " NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 Same as --yes-i-accept-third-party-software\n"
printf " NEMOCLAW_SANDBOX_NAME Sandbox name to create/use\n"
printf " NEMOCLAW_PROVIDER build | openai | anthropic | anthropicCompatible\n"
Expand Down
100 changes: 84 additions & 16 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,14 @@ usage() {
printf " ${C_DIM}Options:${C_RESET}\n"
printf " --non-interactive Skip prompts (uses env vars / defaults)\n"
printf " --yes-i-accept-third-party-software Accept the third-party software notice in non-interactive mode\n"
printf " --fresh Discard any failed/interrupted onboarding session and start over\n"
printf " --version, -v Print installer version and exit\n"
printf " --help, -h Show this help message and exit\n\n"
printf " ${C_DIM}Environment:${C_RESET}\n"
printf " NVIDIA_API_KEY API key (skips credential prompt)\n"
printf " NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 Same as --yes-i-accept-third-party-software\n"
printf " NEMOCLAW_NON_INTERACTIVE=1 Same as --non-interactive\n"
printf " NEMOCLAW_FRESH=1 Same as --fresh\n"
printf " NEMOCLAW_SANDBOX_NAME Sandbox name to create/use\n"
printf " NEMOCLAW_SINGLE_SESSION=1 Abort if active sandbox sessions exist\n"
printf " NEMOCLAW_RECREATE_SANDBOX=1 Recreate an existing sandbox\n"
Expand Down Expand Up @@ -1192,22 +1194,85 @@ run_onboard() {
show_usage_notice
info "Running nemoclaw onboard…"
local -a onboard_cmd=(onboard)
if command_exists node && [[ -f "${HOME}/.nemoclaw/onboard-session.json" ]]; then
if node -e '
const fs = require("fs");
const file = process.argv[1];
try {
const data = JSON.parse(fs.readFileSync(file, "utf8"));
const resumable = data && data.resumable !== false;
const status = data && data.status;
process.exit(resumable && status && status !== "complete" ? 0 : 1);
} catch {
process.exit(1);
}
' "${HOME}/.nemoclaw/onboard-session.json"; then
info "Found an interrupted onboarding session — resuming it."
onboard_cmd+=(--resume)
fi
local session_file="${HOME}/.nemoclaw/onboard-session.json"
# --fresh takes precedence over any session state. We forward --fresh to
# `nemoclaw onboard` so the CLI clears the existing session file before
# creating a new one — the install.sh classifier is bypassed entirely.
if [ "${FRESH:-}" = "1" ]; then
info "Starting a fresh onboarding session (--fresh)."
onboard_cmd+=(--fresh)
elif command_exists node && [[ -f "$session_file" ]]; then
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Classify the session: "resume" (auto-attach --resume), "failed"
# (last run reported a step failure — user must choose), "skip"
# (complete / missing / unreadable — nothing to resume), or "corrupt".
local session_state
session_state="$(
node -e '
const fs = require("fs");
let out = "skip";
try {
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (!data || data.resumable === false || data.status === "complete") {
out = "skip";
} else if (data.status === "failed" || data.failure) {
out = "failed";
} else if (data.status === "in_progress") {
out = "resume";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
// Unknown or missing status — do not auto-resume a file we
// cannot classify against what onboard-session.ts actually
// writes (in_progress / failed / complete).
out = "corrupt";
}
} catch {
out = "corrupt";
}
process.stdout.write(out);
' "$session_file" 2>/dev/null || printf "corrupt"
)"
case "$session_state" in
resume)
info "Found an interrupted onboarding session — resuming it."
onboard_cmd+=(--resume)
;;
failed)
# #2430: a previous run failed. The user's provider/inference
# choice may be the cause, so auto-resuming would just loop.
# Refuse in non-interactive mode (no safe default); prompt in
# interactive mode so the user can pick resume vs. fresh.
if [ "${NON_INTERACTIVE:-}" = "1" ]; then
error "Previous onboarding session failed. Re-run with --fresh to discard it, or run 'nemoclaw onboard --resume' to retry the same session."
fi
local _prompt_stdin="/dev/tty"
if [ -t 0 ]; then _prompt_stdin="/dev/stdin"; fi
if [ ! -r "$_prompt_stdin" ]; then
error "Previous onboarding session failed, and no TTY is available to prompt. Re-run with --fresh or run 'nemoclaw onboard --resume'."
fi
info "Previous onboarding session failed."
local _resume_answer=""
while :; do
printf " Resume the failed session, or start fresh? [R/f]: " >&2
if ! IFS= read -r _resume_answer <"$_prompt_stdin"; then
error "Could not read response from TTY. Re-run with --fresh or run 'nemoclaw onboard --resume'."
fi
case "${_resume_answer,,}" in
"" | r | resume)
onboard_cmd+=(--resume)
break
;;
f | fresh)
onboard_cmd+=(--fresh)
break
;;
*) printf " Please answer 'r' or 'f'.\n" >&2 ;;
esac
done
;;
corrupt)
warn "Onboarding session file is unreadable — ignoring and starting fresh."
;;
skip | *) ;;
esac
fi
if [ "${NON_INTERACTIVE:-}" = "1" ]; then
onboard_cmd+=(--non-interactive)
Expand Down Expand Up @@ -1268,10 +1333,12 @@ main() {
# Parse flags
NON_INTERACTIVE=""
ACCEPT_THIRD_PARTY_SOFTWARE=""
FRESH=""
for arg in "$@"; do
case "$arg" in
--non-interactive) NON_INTERACTIVE=1 ;;
--yes-i-accept-third-party-software) ACCEPT_THIRD_PARTY_SOFTWARE=1 ;;
--fresh) FRESH=1 ;;
--version | -v)
local version_suffix
version_suffix="$(installer_version_for_display)"
Expand All @@ -1291,6 +1358,7 @@ main() {
# Also honor env var
NON_INTERACTIVE="${NON_INTERACTIVE:-${NEMOCLAW_NON_INTERACTIVE:-}}"
ACCEPT_THIRD_PARTY_SOFTWARE="${ACCEPT_THIRD_PARTY_SOFTWARE:-${NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE:-}}"
FRESH="${FRESH:-${NEMOCLAW_FRESH:-}}"
export NEMOCLAW_NON_INTERACTIVE="${NON_INTERACTIVE}"
export NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE="${ACCEPT_THIRD_PARTY_SOFTWARE}"

Expand Down
47 changes: 47 additions & 0 deletions src/lib/onboard-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("onboard command", () => {
).toEqual({
nonInteractive: true,
resume: true,
fresh: false,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: true,
Expand All @@ -56,6 +57,7 @@ describe("onboard command", () => {
).toEqual({
nonInteractive: false,
resume: false,
fresh: false,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: true,
Expand All @@ -78,6 +80,7 @@ describe("onboard command", () => {
expect(runOnboard).toHaveBeenCalledWith({
nonInteractive: false,
resume: true,
fresh: false,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: false,
Expand Down Expand Up @@ -121,6 +124,7 @@ describe("onboard command", () => {
).toEqual({
nonInteractive: false,
resume: true,
fresh: false,
recreateSandbox: false,
fromDockerfile: "/tmp/Custom.Dockerfile",
acceptThirdPartySoftware: false,
Expand All @@ -129,6 +133,47 @@ describe("onboard command", () => {
});
});

it("parses --fresh and surfaces it as fresh=true", () => {
expect(
parseOnboardArgs(
["--fresh"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: exitWithCode,
},
),
).toEqual({
nonInteractive: false,
resume: false,
fresh: true,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: false,
agent: null,
dangerouslySkipPermissions: false,
});
});

it("rejects --resume and --fresh together", () => {
const errors: string[] = [];
expect(() =>
parseOnboardArgs(
["--resume", "--fresh"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: (message = "") => errors.push(message),
exit: exitWithPrefixedCode,
},
),
).toThrow("exit:1");
expect(errors.join("\n")).toContain("--resume and --fresh are mutually exclusive");
});

it("exits when --from is missing its Dockerfile path", () => {
expect(() =>
parseOnboardArgs(
Expand Down Expand Up @@ -178,6 +223,7 @@ describe("onboard command", () => {
).toEqual({
nonInteractive: false,
resume: false,
fresh: false,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: false,
Expand Down Expand Up @@ -225,6 +271,7 @@ describe("onboard command", () => {
expect(runOnboard).toHaveBeenCalledWith({
nonInteractive: false,
resume: false,
fresh: false,
recreateSandbox: false,
fromDockerfile: null,
acceptThirdPartySoftware: false,
Expand Down
15 changes: 13 additions & 2 deletions src/lib/onboard-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export interface OnboardCommandOptions {
nonInteractive: boolean;
resume: boolean;
fresh: boolean;
recreateSandbox: boolean;
fromDockerfile: string | null;
acceptThirdPartySoftware: boolean;
Expand All @@ -30,13 +31,14 @@ export interface RunDeprecatedOnboardAliasCommandDeps extends RunOnboardCommandD
const ONBOARD_BASE_ARGS = [
"--non-interactive",
"--resume",
"--fresh",
"--recreate-sandbox",
"--dangerously-skip-permissions",
];

function onboardUsageLines(noticeAcceptFlag: string): string[] {
return [
` Usage: nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--agent <name>] [--dangerously-skip-permissions] [${noticeAcceptFlag}]`,
` Usage: nemoclaw onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--from <Dockerfile>] [--agent <name>] [--dangerously-skip-permissions] [${noticeAcceptFlag}]`,
"",
];
}
Expand Down Expand Up @@ -96,9 +98,18 @@ export function parseOnboardArgs(
exit(1);
}

const resume = parsedArgs.includes("--resume");
const fresh = parsedArgs.includes("--fresh");
if (resume && fresh) {
error(" --resume and --fresh are mutually exclusive.");
printOnboardUsage(error, noticeAcceptFlag);
exit(1);
}

return {
nonInteractive: parsedArgs.includes("--non-interactive"),
resume: parsedArgs.includes("--resume"),
resume,
fresh,
recreateSandbox: parsedArgs.includes("--recreate-sandbox"),
fromDockerfile,
acceptThirdPartySoftware:
Expand Down
Loading
Loading