Skip to content

fix(destroy): clean up orphaned sandbox Docker images#2176

Merged
cv merged 4 commits into
mainfrom
fix/gc-orphan-sandbox-images
Apr 21, 2026
Merged

fix(destroy): clean up orphaned sandbox Docker images#2176
cv merged 4 commits into
mainfrom
fix/gc-orphan-sandbox-images

Conversation

@ericksoa

@ericksoa ericksoa commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Track sandbox Docker image tags in the registry (imageTag field on SandboxEntry) so they can be cleaned up on destroy and rebuild
  • Add nemoclaw gc command to find and remove orphaned openshell/sandbox-from:* images not associated with any registered sandbox
  • Fix removeSandboxImage() to only report success when docker rmi actually succeeds, and remove redundant registry lookup in the onboard recreate path

Test plan

  • test/image-cleanup.test.ts — 13 structural tests verifying cleanup wiring (all pass)
  • test/registry.test.ts — 3 new tests for imageTag storage, default null, and updateSandbox (all pass)
  • Full test suite: 1947 passed, 7 failed (all 7 are pre-existing environment failures on main)
  • Pre-commit hooks pass (lint, format, gitleaks, markdownlint, commitlint)
  • Manual: run nemoclaw gc --dry-run on a host with orphaned images
  • Manual: verify nemoclaw <name> destroy removes the sandbox image

Summary by CodeRabbit

  • New Features

    • Added nemoclaw gc with --dry-run, --yes, and --force to discover and remove orphaned sandbox images.
  • Improvements

    • nemoclaw <name> destroy now also removes the host-side Docker image produced during onboarding.
    • nemoclaw <name> rebuild removes the old sandbox image before recreating.
    • Sandbox records now track image tags to enable cleanup.
  • Documentation

    • Updated command reference and help text.
  • Tests

    • Added tests for image-tag tracking, teardown/recreate cleanup, and the gc command.

ericksoa and others added 2 commits April 21, 2026 04:57
Add imageTag field to SandboxEntry to track which openshell/sandbox-from:<timestamp>
image belongs to each sandbox. Remove the image during destroy, rebuild, and onboard
recreate flows. Add `nemoclaw gc` command to find and remove orphaned images not
associated with any registered sandbox.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix removeSandboxImage to only log success when docker rmi succeeds
(the try/catch was dead code since run() with ignoreError never throws).
Reuse previousEntry in onboard recreate path instead of a redundant
registry lookup. Fix test regex for sandboxRebuild body extraction.
Add nemoclaw gc to docs/reference/commands.md.
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Records sandbox build image tags during onboarding, removes host-side sandbox images when destroying or rebuilding sandboxes, and adds a new nemoclaw gc CLI command to detect and delete orphaned openshell/sandbox-from:* images.

Changes

Cohort / File(s) Summary
Documentation
.agents/skills/nemoclaw-user-reference/references/commands.md, docs/reference/commands.md
Documented that destroy and rebuild remove host-side Docker images; added `nemoclaw gc [--dry-run] [--yes
Registry
src/lib/registry.ts
Added optional `imageTag?: string
Onboarding
src/lib/onboard.ts
Derive a single buildId per create, set imageTag = openshell/sandbox-from:${buildId} on registration, and remove previous host image when previousEntry.imageTag exists during recreate.
CLI Core
src/nemoclaw.ts
Added global gc command and garbageCollectImages(); added removeSandboxImage(); integrated image removal into sandboxDestroy/sandboxRebuild; gc lists host images, cross-references registry, supports --dry-run and `--yes
Tests
test/image-cleanup.test.ts, test/registry.test.ts
New Vitest suites verifying imageTag persistence and updates, buildId usage, presence/order of removeSandboxImage() calls, and gc CLI behavior (listing, dry-run, confirmation flags).

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as Nemoclaw CLI
    participant Registry as Sandbox Registry
    participant Docker as Docker Daemon

    User->>CLI: nemoclaw gc
    CLI->>Docker: docker images openshell/sandbox-from:*
    Docker-->>CLI: host image list
    CLI->>Registry: listSandboxes()
    Registry-->>CLI: sandboxes with imageTag
    CLI->>CLI: compute orphaned = hostTags - registry.imageTags
    CLI->>User: show orphaned (or --dry-run report)
    alt user confirms or --yes/--force
        CLI->>Docker: docker rmi <orphaned_tag>
        Docker-->>CLI: removal result
    end
    CLI-->>User: report success/failure counts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I built a tag at morning's light,

Old images hid from sight.
gc sniffs, I nudge with gentle paw,
Docker ghosts fall — tidy the law.
Hop, clean, and bounce; the sandbox' right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% 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 'fix(destroy): clean up orphaned sandbox Docker images' accurately captures the primary change: adding Docker image cleanup functionality during sandbox destruction and introducing a garbage collection command.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/gc-orphan-sandbox-images

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

@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: 4

🧹 Nitpick comments (2)
test/image-cleanup.test.ts (2)

92-125: Add at least one behavior-level test for nemoclaw gc.

These assertions only verify source text, so they will still pass if the Docker parsing, prompt flow, or exit-status behavior regresses. A small unit around garbageCollectImages() with mocked spawnSync, registry.listSandboxes(), and askPrompt() would protect the actual contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image-cleanup.test.ts` around lines 92 - 125, Add a behavior-level unit
test that invokes the async function garbageCollectImages with controlled mocks
instead of only asserting source text: stub child_process.spawnSync (or the
module symbol used) to capture docker calls, stub registry.listSandboxes to
return a set of in-use tags, and stub askPrompt to simulate user responses for
both dry-run and --yes flows; then assert that garbageCollectImages queries the
registry, calls spawnSync to remove only images not returned by
registry.listSandboxes (or only logs them in dry-run), and that the prompt
short-circuits deletions when the user declines. Target the function
garbageCollectImages and the symbols spawnSync / registry.listSandboxes /
askPrompt in your test so the behavior (deletion calls, dry-run, and prompt/yes
handling) is verified rather than just source presence.

4-6: Remove the GitHub issue URL from the source comment.

Please replace this with a plain issue ID/reference or local context. As per coding guidelines "Do not add links to third-party code repositories, community collections, or unofficial resources."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image-cleanup.test.ts` around lines 4 - 6, The top-of-file comment that
ends with the GitHub URL should not include external links; update the comment
following "Verify that sandbox lifecycle operations clean up host-side Docker
images." to remove the full GitHub URL and replace it with a plain issue
reference or local context (e.g., "See issue `#2086`" or "internal ticket
NEMO-2086") so the comment remains informative without an external link.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/skills/nemoclaw-user-reference/references/commands.md:
- Line 207: The change was made to a generated skill file
(.agents/skills/nemoclaw-user-reference/references/commands.md) which will be
overwritten; instead, update the canonical documentation under docs/ (edit the
source doc that contains the command description) and then regenerate the skill
files using the provided script: run python3 scripts/docs-to-skills.py docs/
.agents/skills/ --prefix nemoclaw-user to propagate your edits to
.agents/skills/nemoclaw-user-*/*.md (apply the same fix for the other affected
ranges noted).

In `@docs/reference/commands.md`:
- Around line 522-529: Update the `nemoclaw gc` synopsis to include the
`--force` alias so it matches the flag table; change the usage line that
currently reads "$ nemoclaw gc [--dry-run] [--yes]" to include `--force` (e.g.
"$ nemoclaw gc [--dry-run] [--yes|--force]" or list both flags) so the CLI
reference and flag table are consistent.

In `@src/lib/onboard.ts`:
- Around line 3401-3405: The current recreate path calls run(["docker", "rmi",
previousEntry.imageTag], { ignoreError: true }) which can silently fail and
leave an orphaned image while the registry entry is removed; change this to
attempt image removal without ignoreError (or capture and check the run
result/throw) and only proceed with removing/updating the registry entry if the
docker rmi succeeds; specifically, update the block around runOpenshell("sandbox
delete", ...) and run(... "docker rmi" ...) to call run/docker rmi non-ignored,
check for error/exit code (or catch the thrown error) and on failure log the
error and abort/return so the registry entry is not removed, using the existing
symbols sandboxName, previousEntry.imageTag, runOpenshell and run to locate and
implement the check.

In `@src/nemoclaw.ts`:
- Around line 2704-2724: The cleanup loop over orphans uses spawnSync("docker",
["rmi", img.tag"], ...) and tallies removed and failed but always lets the
process exit zero; modify the end of the gc routine to exit non-zero when any
removal failed: after the loop that updates removed and failed (variables
removed, failed and the for..of loop over orphans), call process.exit(1) (or
throw an Error) when failed > 0 so the command returns a non-zero status; keep
existing log messages but ensure success still exits 0 by only exiting non-zero
on failed > 0.

---

Nitpick comments:
In `@test/image-cleanup.test.ts`:
- Around line 92-125: Add a behavior-level unit test that invokes the async
function garbageCollectImages with controlled mocks instead of only asserting
source text: stub child_process.spawnSync (or the module symbol used) to capture
docker calls, stub registry.listSandboxes to return a set of in-use tags, and
stub askPrompt to simulate user responses for both dry-run and --yes flows; then
assert that garbageCollectImages queries the registry, calls spawnSync to remove
only images not returned by registry.listSandboxes (or only logs them in
dry-run), and that the prompt short-circuits deletions when the user declines.
Target the function garbageCollectImages and the symbols spawnSync /
registry.listSandboxes / askPrompt in your test so the behavior (deletion calls,
dry-run, and prompt/yes handling) is verified rather than just source presence.
- Around line 4-6: The top-of-file comment that ends with the GitHub URL should
not include external links; update the comment following "Verify that sandbox
lifecycle operations clean up host-side Docker images." to remove the full
GitHub URL and replace it with a plain issue reference or local context (e.g.,
"See issue `#2086`" or "internal ticket NEMO-2086") so the comment remains
informative without an external link.
🪄 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: Pro Plus

Run ID: ea4bc554-6213-44b9-8e89-bfb1d3e8b2b3

📥 Commits

Reviewing files that changed from the base of the PR and between 5c4c48a and 0664bb3.

📒 Files selected for processing (7)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • docs/reference/commands.md
  • src/lib/onboard.ts
  • src/lib/registry.ts
  • src/nemoclaw.ts
  • test/image-cleanup.test.ts
  • test/registry.test.ts

Comment thread .agents/skills/nemoclaw-user-reference/references/commands.md
Comment thread docs/reference/commands.md
Comment thread src/lib/onboard.ts
Comment thread src/nemoclaw.ts
- Include --force alias in nemoclaw gc synopsis for consistency
- Warn when docker rmi fails in onboard recreate path
- Exit non-zero from nemoclaw gc when any image removal fails
- Include docker stderr in gc failure messages

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/onboard.ts (1)

3762-3774: ⚠️ Potential issue | 🟠 Major

Legacy reused sandboxes still look orphaned to the new GC flow.

imageTag is only written on fresh registerSandbox() calls. The reuse branch returns earlier, so older registry entries keep imageTag: null. On an upgraded host, nemoclaw gc will misclassify those still-live openshell/sandbox-from:* images as orphaned and hit deterministic docker rmi failures, which now make gc exit non-zero. Please backfill imageTag for reused sandboxes, or make GC exclude images referenced by live sandboxes before relying on registry association alone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard.ts` around lines 3762 - 3774, The reuse branch currently
returns before setting imageTag, leaving legacy registry entries with
imageTag:null and causing GC to misidentify live images as orphaned; modify the
reuse path (the code that skips calling registry.registerSandbox when reusing
sandboxes) to backfill the sandbox's imageTag field to
`openshell/sandbox-from:${buildId}` (or update the existing registry entry) so
that registry entries for reused sandboxes get imageTag populated; look for the
reuse-return logic near registry.registerSandbox and ensure the same
buildId-based imageTag is written for reused sandboxes before returning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/onboard.ts`:
- Around line 3598-3604: The code generates a synthetic buildId and calls
patchStagedDockerfile(stagedDockerfile, model, chatUiUrl, buildId, provider) but
then persists an image tag regardless of whether the staged Dockerfile actually
consumed the ARG; modify onboard.ts to verify that patchStagedDockerfile
performed the replacement (e.g., have patchStagedDockerfile return a boolean or
the patched content and then check for "ARG NEMOCLAW_BUILD_ID" or the buildId
string) and only compute/persist imageTag (openshell/sandbox-from:${buildId})
after confirmation; if the replacement did not occur, throw/fail fast and do not
record the synthetic tag (apply the same check around the other occurrence
referenced at lines ~3762-3770).

In `@src/nemoclaw.ts`:
- Around line 2791-2793: The help text for the "nemoclaw gc" synopsis is missing
the --force alias; update the Cleanup/help block where "nemoclaw gc             
Remove orphaned sandbox Docker images (--yes, --dry-run)" is defined to list
both "--yes" and "--force" (e.g., "(- -yes, --force, --dry-run)") so the help
output matches the parser that accepts --force; modify the string/constant that
renders the Cleanup section for the "nemoclaw gc" command accordingly (search
for the Cleanup block or the help-generation routine that contains "nemoclaw gc"
to locate the exact symbol to edit).
- Around line 1981-1988: The helper removeSandboxImage currently swallows docker
rmi failures; update it to surface failures by checking the result when
run(["docker","rmi", sb.imageTag"], { ignoreError: true }) returns a non-zero
status and emit a clear warning (e.g., via console.warn or processLogger.warn)
that includes the sandboxName, sb.imageTag and any error output from the result
(stdout/stderr) so callers of removeSandboxImage (and flows like
destroy/rebuild) can see why image cleanup failed; keep the early-return when
!sb?.imageTag and retain the success log when status === 0.

---

Outside diff comments:
In `@src/lib/onboard.ts`:
- Around line 3762-3774: The reuse branch currently returns before setting
imageTag, leaving legacy registry entries with imageTag:null and causing GC to
misidentify live images as orphaned; modify the reuse path (the code that skips
calling registry.registerSandbox when reusing sandboxes) to backfill the
sandbox's imageTag field to `openshell/sandbox-from:${buildId}` (or update the
existing registry entry) so that registry entries for reused sandboxes get
imageTag populated; look for the reuse-return logic near
registry.registerSandbox and ensure the same buildId-based imageTag is written
for reused sandboxes before returning.
🪄 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: Pro Plus

Run ID: f9040e8c-8152-49fb-bf4d-b64dd1eb8127

📥 Commits

Reviewing files that changed from the base of the PR and between 0664bb3 and 1a4dcf5.

📒 Files selected for processing (4)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • docs/reference/commands.md
  • src/lib/onboard.ts
  • src/nemoclaw.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/reference/commands.md

Comment thread src/lib/onboard.ts
Comment on lines +3598 to 3604
const buildId = String(Date.now());
patchStagedDockerfile(
stagedDockerfile,
model,
chatUiUrl,
String(Date.now()),
buildId,
provider,

@coderabbitai coderabbitai Bot Apr 21, 2026

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.

⚠️ Potential issue | 🟠 Major

Don't persist a synthetic image tag unless the staged Dockerfile actually consumed buildId.

The --from path accepts arbitrary Dockerfiles, and patchStagedDockerfile() only does regex replacement. If a custom Dockerfile does not declare ARG NEMOCLAW_BUILD_ID=..., this code still records openshell/sandbox-from:${buildId} even though that tag was never produced. Destroy/gc will then target the wrong image and leak the real one. Please fail fast when that replacement does not happen, or only store imageTag after confirming the staged Dockerfile contains the expected build-id arg.

Also applies to: 3762-3770

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/onboard.ts` around lines 3598 - 3604, The code generates a synthetic
buildId and calls patchStagedDockerfile(stagedDockerfile, model, chatUiUrl,
buildId, provider) but then persists an image tag regardless of whether the
staged Dockerfile actually consumed the ARG; modify onboard.ts to verify that
patchStagedDockerfile performed the replacement (e.g., have
patchStagedDockerfile return a boolean or the patched content and then check for
"ARG NEMOCLAW_BUILD_ID" or the buildId string) and only compute/persist imageTag
(openshell/sandbox-from:${buildId}) after confirmation; if the replacement did
not occur, throw/fail fast and do not record the synthetic tag (apply the same
check around the other occurrence referenced at lines ~3762-3770).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dismissing — the image tag openshell/sandbox-from:${buildId} is assigned by OpenShell during sandbox create, not by the Dockerfile itself. The NEMOCLAW_BUILD_ID ARG is a cache-busting mechanism; whether or not a custom --from Dockerfile declares it, OpenShell still tags the built image with the same naming convention. The recorded imageTag will match what Docker has on disk regardless of the Dockerfile content.

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.

@ericksoa, thanks for the clarification — that makes sense. Since OpenShell assigns the openshell/sandbox-from:<buildId> tag as part of its sandbox create convention (independent of the Dockerfile ARG), the recorded imageTag will always correspond to the actual Docker image on disk. My concern about the --from path was based on a wrong assumption about who controls the naming. Noted.


✏️ Learnings added
Learnt from: ericksoa
Repo: NVIDIA/NemoClaw PR: 2176
File: src/lib/onboard.ts:3598-3604
Timestamp: 2026-04-21T13:06:45.715Z
Learning: In `src/lib/onboard.ts` (NemoClaw, TypeScript), the image tag `openshell/sandbox-from:${buildId}` is assigned by OpenShell during `sandbox create`, not derived from the Dockerfile's `ARG NEMOCLAW_BUILD_ID`. The `NEMOCLAW_BUILD_ID` ARG is solely a Docker layer cache-busting mechanism. The recorded `imageTag` in the sandbox registry will always match what Docker has on disk regardless of whether a custom `--from` Dockerfile declares the ARG. Do not flag the imageTag persistence as unsafe when the Dockerfile doesn't contain `ARG NEMOCLAW_BUILD_ID`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: KimBioInfoStudio
Repo: NVIDIA/NemoClaw PR: 0
File: :0-0
Timestamp: 2026-04-21T04:56:00.062Z
Learning: In `test/policies.test.ts` (NemoClaw, TypeScript), the `NEMOCLAW_OPENSHELL_BIN` environment variable set-then-`delete` pattern around `buildPolicySetCommand` tests is pre-existing on `main` and not introduced by any single PR. Do not flag missing test hermeticity (e.g., missing `afterEach`/`beforeEach` teardown) in that test block as a new issue in PRs that only reformat assertions in that area.

Learnt from: laitingsheng
Repo: NVIDIA/NemoClaw PR: 2139
File: src/nemoclaw.ts:1669-1746
Timestamp: 2026-04-21T04:21:52.946Z
Learning: In `src/nemoclaw.ts` (NemoClaw, TypeScript), sandbox-scoped action handlers such as `sandboxChannelsAdd`, `sandboxChannelsRemove`, `sandboxPolicyAdd`, and `sandboxPolicyRemove` intentionally do not validate unknown flags or extra positional arguments. They follow a shared convention of using `args.find((arg) => !arg.startsWith("-"))` to extract the first positional arg, mirroring the `policy add`/`policy remove` pattern. Do not flag missing strict arg validation in these handlers as an issue.

Learnt from: KimBioInfoStudio
Repo: NVIDIA/NemoClaw PR: 0
File: :0-0
Timestamp: 2026-04-21T04:56:00.062Z
Learning: In `src/nemoclaw.ts` and `src/lib/policies.ts` (NemoClaw, TypeScript), the `--dry-run` flag for policy commands intentionally previews only the endpoints that would be opened/removed without rendering a merged-policy diff. This contract is shared between `policy-add` and `policy-remove` for consistency. Any enhancement to show a merged-policy diff should be done in a dedicated follow-up that updates both commands together. Do not flag the absence of a merged-policy diff under `--dry-run` as an issue.

Comment thread src/nemoclaw.ts
Comment thread src/nemoclaw.ts
Surface docker rmi failures in removeSandboxImage() so destroy/rebuild
flows show a warning with a nemoclaw gc hint. Add --force alias to the
gc help text to match the parser and docs.

@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 the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 2641-2654: The failure branch in garbageCollectImages currently
logs a generic error when spawnSync for Docker images fails; update that branch
to include imagesResult.stderr (and imagesResult.stdout if present) in the
process output so daemon/permission/path errors are visible—locate the
imagesResult variable in garbageCollectImages and change the console.error call
to print a descriptive message plus imagesResult.stderr and imagesResult.stdout
(or their fallbacks) before calling process.exit(1).
🪄 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: Pro Plus

Run ID: 5d04861a-6b12-47e5-b52d-70df19bf0091

📥 Commits

Reviewing files that changed from the base of the PR and between 1a4dcf5 and 669dd3a.

📒 Files selected for processing (1)
  • src/nemoclaw.ts

Comment thread src/nemoclaw.ts
Comment on lines +2641 to +2654
async function garbageCollectImages(args = []) {
const dryRun = args.includes("--dry-run");
const skipConfirm = args.includes("--yes") || args.includes("--force");

// 1. List all openshell/sandbox-from images on the host
const imagesResult = spawnSync(
"docker",
["images", "--filter", "reference=openshell/sandbox-from", "--format", "{{.Repository}}:{{.Tag}}\t{{.Size}}"],
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] },
);
if (imagesResult.status !== 0) {
console.error(" Failed to query Docker images. Is Docker running?");
process.exit(1);
}

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.

⚠️ Potential issue | 🟡 Minor

Include Docker error details on image discovery failure.

At Line 2651, the failure path prints a generic message only. Include stderr/stdout to make daemon/permission/path issues actionable.

Suggested patch
   if (imagesResult.status !== 0) {
-    console.error("  Failed to query Docker images. Is Docker running?");
-    process.exit(1);
+    const details = `${imagesResult.stderr || imagesResult.stdout || ""}`.trim();
+    console.error(
+      `  Failed to query Docker images. Is Docker running?${details ? `: ${details}` : ""}`,
+    );
+    process.exit(imagesResult.status || 1);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nemoclaw.ts` around lines 2641 - 2654, The failure branch in
garbageCollectImages currently logs a generic error when spawnSync for Docker
images fails; update that branch to include imagesResult.stderr (and
imagesResult.stdout if present) in the process output so daemon/permission/path
errors are visible—locate the imagesResult variable in garbageCollectImages and
change the console.error call to print a descriptive message plus
imagesResult.stderr and imagesResult.stdout (or their fallbacks) before calling
process.exit(1).

@cv cv merged commit 41b405c into main Apr 21, 2026
20 checks passed
@cv cv added the v0.0.22 label Apr 21, 2026
@miyoungc miyoungc mentioned this pull request Apr 22, 2026
13 tasks
miyoungc added a commit that referenced this pull request Apr 22, 2026
## Summary

Bumps the published doc version to `0.0.22` and documents the
user-visible CLI behavior changes to `nemoclaw <name> connect` that
landed since v0.0.21. Drafted via the `nemoclaw-contributor-update-docs`
skill against commits in `v0.0.21..origin/main`, filtered through
`docs/.docs-skip`.

## Changes

- **`docs/project.json`** and **`docs/versions1.json`**: bump the
published version from `0.0.20` to `0.0.22`; insert a `0.0.21` entry
into the version list so the history stays contiguous.
- **`docs/reference/commands.md`** → `nemoclaw <name> connect`: document
two new behaviors.
- Readiness poll with `NEMOCLAW_CONNECT_TIMEOUT` (integer seconds;
default `120`) that replaces the silent hang when the sandbox is not yet
`Ready` — right after onboarding, while the 2.4 GB image is still
pulling (#466).
- Post-connect hint is now agent-aware, names the correct TUI command
for the sandbox's agent, and tells you to use `/exit` to leave the chat
before `exit` returns you to the host shell (#2080).

Feature PRs that shipped their own docs in the same commit are
intentionally not re-documented here:

- `channels list/add/remove` (#2139) — command reference and the
"`openclaw channels` blocked inside the sandbox" troubleshooting entry
landed with the feature.
- `nemoclaw gc` (#2176) — documented as part of the destroy/rebuild
image cleanup PR.

Skipped per `docs/.docs-skip`:

- `e6bad533 fix(shields): verify config lock and fail hard on re-lock
failure (#2066)` — matched `skip-features: src/lib/shields.ts`.

Other commits in the range (#2141 OpenShell version bump, #1819 plugin
banner live inference probe, #2085 / #2146 Slack Socket Mode fixes,
#2110 axios proxy fix, #1818 NIM curl timeouts, #1824 onboard gateway
bootstrap recovery, and assorted CI / test / install plumbing) are
internal behavior refinements with no doc-relevant surface change.

## 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)

## Verification

- [x] `npx prek run --all-files` passes for the modified files via the
pre-commit hook, including `Regenerate agent skills from docs` (source ↔
generated parity confirmed)
- [ ] `npm test` passes — skipped; the one pre-existing
`test/cli.test.ts > unknown command exits 1` failure on `origin/main` is
unrelated to these markdown/JSON-only changes
- [ ] Tests added or updated for new or changed behavior — n/a, doc-only
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only) — not run
locally
- [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)
— n/a, no new pages

## AI Disclosure

- [x] AI-assisted — tool: Claude Code

---
Signed-off-by: Miyoung Choi <miyoungc@nvidia.com>

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

* **New Features**
* `connect` now displays the sandbox phase while waiting for readiness
and honors a configurable timeout via NEMOCLAW_CONNECT_TIMEOUT (default
120s).
* TTY hints are agent-aware and instruct using `/exit` before returning
to the host shell.

* **Documentation**
  * Command docs updated to describe polling, timeout, and TTY guidance.
* Project/docs metadata updated for versions 0.0.21 and 0.0.22 (package
version bumped to 0.0.22).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output bug-fix PR fixes a bug or regression and removed NemoClaw CLI labels Jun 3, 2026
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants