fix(cli): let strict backup-all skip stranded orphan sandboxes#7290
Conversation
`nemoclaw uninstall --yes` followed by a curl reinstall aborts at the installer strict pre-upgrade backup: the uninstall removes the gateway registration and sandbox container but preserves sandboxes.json, so the stranded record can only ever be skipped, and the strict gate (NEMOCLAW_REQUIRE_ALL_SANDBOX_BACKUPS=1, #6114) exits 1 before the installer recovery phase (recover_preexisting_sandboxes_before_onboard) that knows how to surface orphans can run. Teach backupAll() to classify such records with the #6539 orphan classifier (unobserved on the selected gateway AND persisted binding resolving to that same gateway) and to downgrade the strict abort to a warning only when the sandbox OpenShell-labeled container is definitively absent. Stranded records are tracked separately from `skipped`, so the strict gate keeps failing closed for every genuine skip, and the run ends with orphanedRegistrySummary + orphanedRegistryRemediation so standalone backup-all users get the same destroy/onboard guidance as the recovery phase. The new isSandboxContainerDefinitivelyAbsent() fails closed everywhere absence cannot be proven: non-docker or unknown driver (including a throwing registry read), and a failed or timed-out labeled listing. The listing is status-checked (not findLabeledSandboxContainers, which swallows docker errors) so a dead daemon yields null, never "absent", and it passes ignoreError so the probe cannot process.exit the run. backup-all now also pins its sandbox listing to the selected gateway (same #6114 rationale as upgrade-sandboxes): an unpinned list from a sibling selection must not feed a fail-open stranded decision. Constraints preserved (pinned by tests): - strict gate still aborts on any genuine skip (#6114) - a sandbox bound to a sibling gateway is never claimed stranded - an unobserved sandbox whose container still exists keeps the abort (reconnect race) - real backup failures still re-throw/abort - a registry row with a null/unknown driver keeps the strict abort (fail closed; absence cannot be proven for it) Refs #6520 Signed-off-by: Dongni Yang <dongniy@nvidia.com>
📝 WalkthroughWalkthrough
ChangesStranded orphan backup handling
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant backupAll
participant gatewaySandboxListing
participant isSandboxContainerDefinitivelyAbsent
participant dockerRun
backupAll->>gatewaySandboxListing: capture selected-gateway sandbox list
backupAll->>isSandboxContainerDefinitivelyAbsent: classify sandbox container absence
isSandboxContainerDefinitivelyAbsent->>dockerRun: run labeled docker ps -a probe
dockerRun-->>isSandboxContainerDefinitivelyAbsent: container listing or probe failure
isSandboxContainerDefinitivelyAbsent-->>backupAll: definitive absence or uncertain state
backupAll->>gatewaySandboxListing: confirm stranded candidate
backupAll-->>backupAll: skip, report, or preserve strict failure
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/pluginThe overall coverage in commit 71a02ca in the TypeScript / code-coverage/cliThe overall coverage in commit 71a02ca in the Show a code coverage summary of the most impacted files.
Updated |
PR Review Advisor — InformationalAdvisor assessment: Informational / high confidence Model lanes
Nemotron output stays in workflow artifacts and does not change the assessment above. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: 2 optional E2E recommendations
This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/actions/maintenance.ts (1)
66-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting stranded-orphan classification out of
backupAll.The logic here is correct, but
backupAll(lines 57-260) is already a very large function, and this change adds another self-contained concern (gateway-scoped orphan classification + stranded tracking + summary reporting) directly inline. As per coding guidelines,**/*.{js,ts,tsx}should "Keep function complexity low and avoid introducing unnecessary complexity hotspots." Pulling the orphan-classification setup (lines 79-96) and/or the stranded-check branch (115-120) into a small named helper would keepbackupAll's control flow easier to follow without changing behavior.Also applies to: 105-120, 224-227
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/actions/maintenance.ts` around lines 66 - 96, Extract the gateway-scoped orphan classification and stranded-sandbox handling from backupAll into a small named helper, covering the setup around classifyOrphanedRegistrySandboxes and the stranded-check branch and summary reporting. Have the helper accept the existing sandbox/listing context and return the values backupAll needs, preserving gateway selection, container-absence checks, and current behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/lib/actions/maintenance.ts`:
- Around line 66-96: Extract the gateway-scoped orphan classification and
stranded-sandbox handling from backupAll into a small named helper, covering the
setup around classifyOrphanedRegistrySandboxes and the stranded-check branch and
summary reporting. Have the helper accept the existing sandbox/listing context
and return the values backupAll needs, preserving gateway selection,
container-absence checks, and current behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 2b8ee3a3-c3ca-42c2-b33e-db072b62a2ea
📒 Files selected for processing (4)
src/lib/actions/maintenance.test.tssrc/lib/actions/maintenance.tssrc/lib/actions/sandbox/stopped-sandbox-backup.test.tssrc/lib/actions/sandbox/stopped-sandbox-backup.ts
Address PR Review Advisor findings on #7290: - PRA-1: the orphan classification was only as fresh as the pre-loop listing, and the backup loop can run for minutes. backup-all now re-lists the selected gateway after the loop (same two-phase confirmation as upgrade-sandboxes, #6114) and reverts any stranded candidate the gateway observes again to the genuine strict skip it would otherwise have been. - PRA-2: document the exemption source-of-truth boundary in code: the stranded state is created by `nemoclaw uninstall` (which preserves sandboxes.json by design), backup-all must not reconcile the registry itself, and the exemption is removable once install/uninstall reconciles the registry or the installer recovery phase runs before the strict pre-upgrade backup. - Apply biome format to the absence tests. Refs #6520 Signed-off-by: Dongni Yang <dongniy@nvidia.com>
Signed-off-by: Charan Jagwani <cjagwani@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
<!-- markdownlint-disable MD041 --> ## Summary Adds the canonical pre-tag `## v0.0.95` release entry to `docs/changelog/2026-07-24.mdx`, before the existing v0.0.94 entry. The entry summarizes approved user-visible changes merged since v0.0.94 and excludes internal-only prerequisites. ## Changes - Adds the v0.0.95 summary and detailed bullets for gateway lifecycle, recovery, state transfer, inference compatibility, sandbox security, Discord policy, and E2E evidence. - Links each user-facing theme to the most specific published documentation. - Records the release entry in the shared native changelog used by the OpenClaw, Hermes, and Deep Agents guides. Source summary: - [#7246](#7246), [#7228](#7228), [#7267](#7267), [#7489](#7489), [#7509](#7509), [#7351](#7351), and [#7290](#7290) -> `docs/changelog/2026-07-24.mdx`: Gateway authority, forward teardown and retry, managed recovery, Hermes restart recovery, scoped uninstall, and orphan-aware backup behavior. - [#7344](#7344) and [#7416](#7416) -> `docs/changelog/2026-07-24.mdx`: Atomic SQLite restore and host download verification. - [#7476](#7476), [#7347](#7347), [#7281](#7281), [#7485](#7485), [#7491](#7491), and [#7422](#7422) -> `docs/changelog/2026-07-24.mdx`: Windows Ollama reuse, CDI fallback, bounded OpenRouter connection setup, Nemotron-3 request compatibility, and managed Deep Agents retry and provider-error behavior. - [#6884](#6884), [#7481](#7481), [#6878](#6878), [#7467](#7467), [#7502](#7502), [#7503](#7503), [#7504](#7504), and [#7486](#7486) -> `docs/changelog/2026-07-24.mdx`: Trusted base-image overrides, local rebuild images, runtime validation, config preservation, reviewed package updates, and fewer final-image payload layers. - [#7303](#7303) -> `docs/changelog/2026-07-24.mdx`: Scoped Discord application-command management. - [#7488](#7488), [#7465](#7465), [#7497](#7497), [#7464](#7464), [#7501](#7501), [#7494](#7494), and [#7493](#7493) -> `docs/changelog/2026-07-24.mdx`: Selected-test risk signals, retry cleanup, full root-image validation, direct-main Hermes setup, executed PR-gate evidence, nightly history, and runner wait reporting. - [#7447](#7447) is an internal pinned-runtime prerequisite and is intentionally excluded from canonical supported-integration documentation. - [#7370](#7370) adds maintainer-only advisory reconciliation tooling and does not change supported user behavior. - [#7495](#7495) updates existing documentation and does not add a new v0.0.95 behavior claim. ## Type of Change - [ ] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [x] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [ ] Tests added or updated for changed behavior - [x] Existing tests cover changed behavior — justification: `test/changelog-docs.test.ts` validates the dated changelog structure, heading uniqueness, and published links. - [ ] Tests not applicable — justification: - [x] Docs updated for user-facing behavior changes - [ ] Docs not applicable — justification: - [ ] 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: - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `docs-updated` - Evidence: `docs/changelog/2026-07-24.mdx`; writing rules, documentation style, factual release meaning, and published links reviewed at exact head `58b02f2bf`. - Agent: Codex documentation writer reviewer <!-- docs-review-head-sha: 58b02f2 --> <!-- docs-review-agents-blob-sha: 9c9b36d --> ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: - Station profile/scenario: - Result: - Supporting evidence: ## 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 check:diff` passed 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 or justification: `npx vitest run test/changelog-docs.test.ts` passed 6 tests. - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) — the build passed with 0 errors and 2 Fern warnings. - [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) --- Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a new v0.0.95 changelog entry above v0.0.94. * Documented improved externally supervised gateway lifecycle ownership. * Improved snapshot restore reliability and SQLite state handling. * Tightened CLI `backup-all` behavior and host artifact verification. * Updated Windows onboarding guidance (including Ollama service reuse and CDI directory fallback). * Noted inference compatibility fixes, deeper agent failure classification, stricter base-image validation, updated Discord bot command permissions, and refined E2E release automation evidence handling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com>
Summary
Reopen follow-up for #6520:
nemoclaw uninstall --yesfollowed by a curl reinstall hard-aborts at the installer strict pre-upgrade backup. The uninstall removes the gateway registration and the sandbox container but preservessandboxes.json, so the stranded record can only ever be skipped — and the strict gate (NEMOCLAW_REQUIRE_ALL_SANDBOX_BACKUPS=1, #6114) exits 1 before the installer recovery phase (recover_preexisting_sandboxes_before_onboard), the phase that knows how to surface orphans, ever runs. The #6539 orphan classifier is wired only intoupgrade-sandboxes, which is downstream of the aborting backup.What changed
backupAll()now classifies stranded records with the existingclassifyOrphanedRegistrySandboxes(unobserved on the selected gateway AND persisted binding resolving to that same gateway), gated on the newisSandboxContainerDefinitivelyAbsent(). Stranded records are tracked separately fromskipped, so the strict gate keeps failing closed for every genuine skip, and the run ends withorphanedRegistrySummary+orphanedRegistryRemediation(same destroy/onboard guidance as the recovery phase). End-to-end, the installer now proceeds to its recovery phase, which reports the orphans and yields "completed with warnings" instead of a hard abort.upgrade-sandboxes— and any candidate the gateway observes again reverts to a genuine strict skip. Container absence is itself checked per candidate at skip time.isSandboxContainerDefinitivelyAbsent()fails closed everywhere absence cannot be proven: non-docker or unknown driver (including a throwing registry read), and a failed or timed-out labeled listing. The listing is status-checked (docker ps -awith the OpenShell labels) rather than reusingfindLabeledSandboxContainers, which swallows docker errors — a dead daemon yieldsnull, never "absent" — and passesignoreErrorso the probe cannotprocess.exitthe run.backup-allnow pins its sandbox listing to the selected gateway (same [All Platforms][Upgrade] v0.0.55 → v0.0.76 leaves pre-existing sandboxes stuck in Provisioning/Error — user data inaccessible until manual rebuild #6114 rationale and idiom asupgrade-sandboxes): an unpinned list taken from a sibling gateway selection must not feed a fail-open stranded decision.nemoclaw uninstallpreservessandboxes.jsonby design), source-fix constraint (backup-all must not reconcile the registry; record removal is owned by the recovery phase destroy/onboard flow), and removal condition (install/uninstall registry reconciliation, or running the recovery phase before the strict backup).Constraints preserved (pinned by tests)
ORPHANED_SANDBOX_MARKER/ summary / remediation wording unchanged; install.sh greps the marker only from the recovery-phase log, which this change does not touch.Review responses
reviewed-npm-auditfailure is upstream-wide (also fails on test(images): add security revision container verifier #7289) and is being remediated by fix(security): remediate OpenClaw 2026.6.10 transitive dependencies #7286; this PR adds no dependencies.Verification
vitest:maintenance.test.ts(32),stopped-sandbox-backup.test.ts(24),orphan-detection.test.ts,upgrade-sandboxes-recovery.test.ts,openshell-sandbox-list.test.ts— 108 tests green; installer lanetest/install-orphaned-sandbox-recovery.test.ts(8, drives strict backup through recovery and the real install.sh orphan grep) green;typecheck:cli,lint, andbiome checkclean. The new maintenance tests pinGATEWAY_PORTso they stay green under an exportedNEMOCLAW_GATEWAY_PORT.0 skipped+ exit 0 under strict mode (through both listings); same record bound togatewayPort: 9999→ strict abort exit 1 with no orphan claim; same record with a labeled container present → strict abort exit 1 with no orphan claim.Refs #6520
Signed-off-by: Dongni Yang dongniy@nvidia.com
🤖 Generated with Claude Code
Summary by CodeRabbit
Takeover review response
backup-all, orphan recovery, and the completed-with-warnings result in order.Documentation Writer Review
no-docs-needed71a02caebe9f. No documentation paths changed; existing docs already cover strict backup and stranded-record recovery and remediation.Signed-off-by: Julie Yaunches jyaunches@nvidia.com