Skip to content

fix(shields): recover a locked sandbox whose startup never completed - #8460

Closed
rluo8 wants to merge 8 commits into
mainfrom
fix/8304-shields-restart-escape
Closed

fix(shields): recover a locked sandbox whose startup never completed#8460
rluo8 wants to merge 8 commits into
mainfrom
fix/8304-shields-restart-escape

Conversation

@rluo8

@rluo8 rluo8 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

A shields-up sandbox whose agent gateway fails to start could not lower its shields. The container publishes its startup readiness lease only after the gateway is healthy, so a gateway that never starts leaves no lease, and the config guard then refused the actions that make up shields down. The only exit was rebuild, which destroys and recreates the sandbox. The guard now exposes one dedicated action that unseals both state layers atomically once it proves the startup terminally failed, so shields down followed by start returns the sandbox to Ready.

Related Issue

Fixes #8304

Changes

  • Extract the supervised-start procfs scan in scripts/openclaw-config-guard.py into _openshell_supervised_nonroot_start_census, returning the match count and PID, or None when the topology cannot be established. _openshell_supervised_nonroot_start_is_live keeps its exact prior contract on top of it.
  • Add _openshell_supervised_nonroot_start_is_absent, a positive failed-startup discriminator. An undeterminable topology and a duplicated start both answer False, so it only widens authority when the supervisor is proven stable and proven childless.
  • Add the unlock-failed-startup action. It is the only action the failed-startup escape authorizes, and it is refused whenever the ordinary readiness lease applies. It reconfirms the census under the mutation mutex, unseals the recursive state tree, commits the top-level handoff, and re-seals the tree if the handoff fails.
  • Teach scripts/state-dir-guard.py to accept an inherited mutation-mutex descriptor. Both guards take the same mutex, so the recovery action passes its held descriptor through pass_fds; the child inherits the lock instead of deadlocking on it. A separately opened descriptor blocks on flock and is refused, so this cannot be used to bypass serialization.
  • Give the recovery action a host timeout that outlasts the guard's own fan-out budget, and refuse it on images without the installed guard, where neither the in-image state guard nor the escape's installed_current requirement can be satisfied.
  • Route the host through the ordinary sequence first. Only a startup-not-ready preflight — which is read-only and mutates nothing — falls back to the recovery action. A transition, rollback, contract, parse, or timeout failure surfaces instead of being masked.
  • Document the recovery step for a locked OpenClaw sandbox whose gateway does not return after a restart.
  • Extend the live Shields sandbox target with installed-guard evidence for live-child refusal, childless recovery, recursive state unlock through the inherited mutation lock, restart, and lockdown restoration.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • 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: nine-category maintainer security review passed with no code finding; fix(shields): recover a locked sandbox whose startup never completed #8460 (comment)
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: docs-updated
  • Evidence: docs/manage-sandboxes/recover-rebuild-sandboxes.mdx; independent review approved the scoped recovery conditions and complete operator sequence with no further edits.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: 57 focused TypeScript Shields/guard tests, CLI build/typecheck, and E2E semantic phase validation passed. The installed live target loaded and skipped before sandbox creation because the protected inference secret is unavailable locally; protected live execution is documented as pending.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Rui Luo ruluo@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added recovery support for failed OpenClaw startups, allowing shields to be lowered only after startup has definitively stopped.
    • Recovery safely unlocks required configuration layers and restores protection if the operation fails.
    • Added guidance for restarting sandboxes and re-enabling shields after recovery.
  • Bug Fixes

    • Improved handling of unavailable gateways following container restarts.
    • Added safeguards against active, ambiguous, stale, or invalid startup states.
  • Tests

    • Expanded coverage for recovery gating, timeouts, rollback behavior, and lock validation.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

OpenClaw failed-startup recovery now validates terminal startup failure, shares the mutation lock with the state-directory guard, unlocks configuration and state atomically, and restores both layers on failure. Shields integration, tests, E2E coverage, and recovery documentation expose the new flow.

Changes

OpenClaw recovery flow

Layer / File(s) Summary
Failed-startup authorization
scripts/openclaw-config-guard.py, test/openclaw-config-guard-startup-failure-gate.test.ts
The guard adds stable process census checks, provisional authorization, mutex-held reconfirmation, and the unlock-failed-startup CLI action. Tests cover live, stale, duplicate, foreign, and changing startup states.
Atomic state and config unlock
scripts/openclaw-config-guard.py, scripts/state-dir-guard.py, test/openclaw-config-guard-startup-failure-gate.test.ts
Recovery passes an inherited mutation lock to the state-directory guard. The guard validates descriptor ownership, metadata, inode stability, size, and exclusive locking. Failures relock both state and config layers.
Shields integration and recovery validation
src/lib/shields/*, test/e2e/live/shields-config.test.ts, docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
Shields-down handles startup-not-ready through failed-startup recovery. The integration forwards planJson, uses recovery-specific timeouts, rejects unavailable helper fallback, and documents restart and shields restoration steps. Unit and E2E tests cover the recovery flow.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ShieldsDown
  participant OpenClawConfigGuard
  participant StateDirGuard
  participant Sandbox
  ShieldsDown->>OpenClawConfigGuard: handle startup-not-ready
  OpenClawConfigGuard->>OpenClawConfigGuard: verify failed startup and reconfirm under mutex
  OpenClawConfigGuard->>StateDirGuard: run unlock-failed-startup with plan and lock descriptor
  StateDirGuard-->>OpenClawConfigGuard: return guard result
  OpenClawConfigGuard-->>ShieldsDown: return recovery or rollback error
  ShieldsDown->>Sandbox: restart after successful unlock
Loading

Possibly related PRs

Suggested labels: integration: openclaw, area: security, area: sandbox, area: policy, area: cli, bug-fix

Suggested reviewers: cv, prekshivyas

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.74% 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
Linked Issues check ✅ Passed The changes address issue #8304 by enabling failed-startup recovery without rebuilding the sandbox.
Out of Scope Changes check ✅ Passed The implementation, tests, and documentation directly support the recovery objective in issue #8304.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: recovering a locked sandbox when startup does not complete.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/8304-shields-restart-escape

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

@github-code-quality

github-code-quality Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit f7ca3d1 in the fix/8304-shields-res... branch remains at 96%, unchanged from commit 0d33997 in the main branch.


Updated August 06, 2026 23:39 UTC

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Comment thread scripts/openclaw-config-guard.py Fixed
Comment thread scripts/openclaw-config-guard.py Fixed

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

🧹 Nitpick comments (5)
src/lib/shields/openclaw-config-lock.test.ts (1)

421-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the forwarded plan value, not only the flag.

toContain("--plan-json") passes even if the flag is pushed without its value or with the wrong value. The test title claims the recovery "forwards the plan", so assert the adjacent argument.

💚 Proposed assertion
-    expect(recovery).toContain("--plan-json");
+    expect(recovery?.slice(-2)).toEqual(["--plan-json", '{"version":1}']);
🤖 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/shields/openclaw-config-lock.test.ts` around lines 421 - 426, Update
the recovery assertion around the recorded unlock-failed-startup command to
verify that --plan-json is immediately followed by the expected plan value,
rather than only checking for the flag. Preserve the existing timeout assertions
and use the plan value established by the test setup.
src/lib/shields/index.ts (1)

2138-2155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both branch decisions depend on free-text guard details.

NOT_A_FAILED_STARTUP matches the detail string built in scripts/openclaw-config-guard.py at line 4342, and isOpenClawStartupNotReady matches the rendered [startup-not-ready] code. Neither string is asserted by a shared contract. If the Python detail text is reworded, this branch stops recognizing the refusal and reports a hard failure instead of falling back.

Both mismatch directions fail safely, so this is a maintainability concern rather than a defect. The guard already emits a structured code per issue; runOpenClawConfigGuard flattens it into a message before returning. Consider exposing the parsed GuardIssue codes on OpenClawConfigGuardResult and matching on code === "startup-not-ready".

🤖 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/shields/index.ts` around lines 2138 - 2155, Update
runOpenClawConfigGuard and OpenClawConfigGuardResult to preserve and expose each
parsed GuardIssue code alongside its message, then change
recoverOpenClawFailedStartupShields to identify the not-applicable case using
code === "startup-not-ready" rather than matching NOT_A_FAILED_STARTUP or
rendered free text. Keep non-startup issues surfaced as errors and remove the
obsolete text-based constant and checks.
src/lib/shields/openclaw-config-lock.ts (1)

355-360: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Require planJson locally for unlock-failed-startup.

write-config rejects a missing expectedConfigSha256 before it runs anything privileged. unlock-failed-startup has no equivalent check. If planJson is absent or empty, line 431 silently drops the flag, the guard runs in the sandbox, acquires the mutation mutex, and only then fails with invalid-state-lock-plan.

Add the precondition next to the write-config check. The truthiness test on line 431 also drops an empty string without a diagnostic.

♻️ Proposed precondition
   if (action === "write-config" && !options.expectedConfigSha256) {
     return {
       issues: ["OpenClaw config guard write-config requires expectedConfigSha256"],
       chattrApplied: false,
     };
   }
+  if (action === "unlock-failed-startup" && !options.planJson) {
+    return {
+      issues: ["OpenClaw config guard unlock-failed-startup requires planJson"],
+      chattrApplied: false,
+    };
+  }

Also applies to: 431-431

🤖 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/shields/openclaw-config-lock.ts` around lines 355 - 360, Add a local
precondition alongside the existing write-config validation for
unlock-failed-startup that rejects a missing or empty options.planJson with the
appropriate invalid-state-lock-plan issue before any privileged or
mutex-protected work runs. Update the planJson handling near the guard
invocation so empty values are not silently omitted and instead produce the same
diagnostic.
test/openclaw-config-guard-startup-failure-gate.test.ts (1)

85-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the main() gate, not only the helper gate.

cli_accepts proves that unlock-failed-startup appears in the parser choices and matches the Action union. The tests then exercise _validate_action_readiness and _reconfirm_startup_failure_recovery directly.

Two main() refusals stay unproven:

  • main() raises startup-not-ready when _validate_action_readiness returns False for unlock-failed-startup.
  • main() raises invalid-state-lock-plan when --plan-json is absent.

Both are the last gates before the unseal runs. Add harness cases that call guard.main([...]) with a stubbed identity and assert the emitted issue codes.

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` around lines 85 -
113, Extend the startup-failure recovery harness to exercise guard.main() rather
than only the helper gates. Add cases with a stubbed identity that verify main()
emits startup-not-ready when _validate_action_readiness rejects
unlock-failed-startup, and invalid-state-lock-plan when --plan-json is omitted;
assert the emitted issue codes while preserving the existing helper coverage.
docs/manage-sandboxes/recover-rebuild-sandboxes.mdx (1)

26-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Place the shields guidance before the rebuild guidance.

Line 26 directs the reader to the printed rebuild --yes guidance. The new block at line 29 then tells the reader to lower shields "before reaching for a rebuild". A reader who follows the page in order rebuilds first, which is the outcome this PR removes.

Move the <AgentOnly variant="openclaw"> block above line 26.

🤖 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 `@docs/manage-sandboxes/recover-rebuild-sandboxes.mdx` around lines 26 - 33,
Move the AgentOnly block for OpenClaw shields guidance before the paragraph
directing readers to rebuild missing or failed containers, preserving its
content and placement relative to the surrounding instructions.
🤖 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.

Inline comments:
In `@scripts/openclaw-config-guard.py`:
- Around line 1481-1534: The recovery flow must use one shared deadline instead
of granting each state-dir guard invocation a separate full timeout. In
scripts/openclaw-config-guard.py lines 1481-1534, update _run_state_dir_guard
and its _run_failed_startup_unlock callers to compute the whole-action deadline
and pass only the remaining time to each unlock or relock invocation, ensuring
rollback cannot exceed the host budget. In
src/lib/shields/openclaw-config-lock.ts lines 22-24, set
RECOVERY_CONTAINER_TIMEOUT to cover the guard’s combined unlock-and-rollback
worst case and document both constants as a single coordinated budget.

In `@src/lib/shields/index.ts`:
- Around line 2124-2135: Move the existing shield-lowering doc comment from
above isOpenClawStartupNotReady to immediately above
recoverOpenClawFailedStartupShields, leaving the error-classification helper
with documentation that matches its actual behavior.

---

Nitpick comments:
In `@docs/manage-sandboxes/recover-rebuild-sandboxes.mdx`:
- Around line 26-33: Move the AgentOnly block for OpenClaw shields guidance
before the paragraph directing readers to rebuild missing or failed containers,
preserving its content and placement relative to the surrounding instructions.

In `@src/lib/shields/index.ts`:
- Around line 2138-2155: Update runOpenClawConfigGuard and
OpenClawConfigGuardResult to preserve and expose each parsed GuardIssue code
alongside its message, then change recoverOpenClawFailedStartupShields to
identify the not-applicable case using code === "startup-not-ready" rather than
matching NOT_A_FAILED_STARTUP or rendered free text. Keep non-startup issues
surfaced as errors and remove the obsolete text-based constant and checks.

In `@src/lib/shields/openclaw-config-lock.test.ts`:
- Around line 421-426: Update the recovery assertion around the recorded
unlock-failed-startup command to verify that --plan-json is immediately followed
by the expected plan value, rather than only checking for the flag. Preserve the
existing timeout assertions and use the plan value established by the test
setup.

In `@src/lib/shields/openclaw-config-lock.ts`:
- Around line 355-360: Add a local precondition alongside the existing
write-config validation for unlock-failed-startup that rejects a missing or
empty options.planJson with the appropriate invalid-state-lock-plan issue before
any privileged or mutex-protected work runs. Update the planJson handling near
the guard invocation so empty values are not silently omitted and instead
produce the same diagnostic.

In `@test/openclaw-config-guard-startup-failure-gate.test.ts`:
- Around line 85-113: Extend the startup-failure recovery harness to exercise
guard.main() rather than only the helper gates. Add cases with a stubbed
identity that verify main() emits startup-not-ready when
_validate_action_readiness rejects unlock-failed-startup, and
invalid-state-lock-plan when --plan-json is omitted; assert the emitted issue
codes while preserving the existing helper coverage.
🪄 Autofix

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: Enterprise

Run ID: 03025dce-2084-4d37-a703-5cdd5b3a08c8

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc30f0 and 431d6a2.

📒 Files selected for processing (7)
  • docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
  • scripts/openclaw-config-guard.py
  • scripts/state-dir-guard.py
  • src/lib/shields/index.ts
  • src/lib/shields/openclaw-config-lock.test.ts
  • src/lib/shields/openclaw-config-lock.ts
  • test/openclaw-config-guard-startup-failure-gate.test.ts

Comment thread scripts/openclaw-config-guard.py
Comment thread src/lib/shields/index.ts Outdated
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: Review the warnings below.
Findings: 0 blockers · 1 warning · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 1 warning · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 0 warnings · 0 suggestions

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

3 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • justified — failed-startup recovery at scripts/openclaw-config-guard.py:1500: Keep this term for the childless terminal-startup recovery path.
  • established — startup readiness lease at src/lib/shields/index.ts:2125: Keep the established term for the readiness-marker protocol.
  • established — mutation mutex at scripts/state-dir-guard.py:2390: Keep the established term for the transition lock and its inherited descriptor.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: cloud-inference, managed-image-multiarch-startup, security-posture, shields-config

1 warning · 0 suggestions

Warnings

Warnings do not block.

PRA-1 Warning — Cover the public failed-startup fallback

  • Location: src/lib/shields/index.ts:2178
  • Category: tests
  • Problem: The new fallback from a `startup-not-ready` preflight to `unlock-failed-startup` has no caller-level regression test.
  • Impact: A later change can remove, misclassify, or fail to reconcile the public `shields down` fallback while direct guard tests still pass.
  • Recommendation: Add a focused Shields-flow test that returns `startup-not-ready` from preflight, succeeds for `unlock-failed-startup`, and verifies the public Shields-down flow completes its normal state reconciliation.
  • Verification: Inspect the ordered guard actions and persisted Shields state in a focused `unlockAgentConfigUnderMutationLock` or public Shields-flow test.
  • Test coverage: A caller-level test for `shields down` that simulates `startup-not-ready` preflight, verifies exactly one `unlock-failed-startup` call with the state-lock plan, and asserts the normal Shields-down receipt/state is recorded.
  • Evidence: src/lib/shields/index.ts:2178-2190 adds the preflight catch and recovery-action fallback. test/e2e/live/shields-config.test.ts:967-1035 invokes `unlock-failed-startup` directly in the container. src/lib/shields/openclaw-config-lock.test.ts:404-445 tests guard command wiring only.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Signed-off-by: Rui Luo <ruluo@nvidia.com>

@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.

🧹 Nitpick comments (2)
test/openclaw-config-guard-startup-failure-gate.test.ts (2)

140-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bind the outcome variables before the try blocks.

timeout_code, transaction_error, and timeout_transaction_code are assigned only in the except guard.GuardError branch. If the guard stops raising, the harness fails at line 199 with NameError instead of reporting the missing error. That hides the regression the test is meant to catch. Initialize each name to a sentinel so the JSON payload shows the real outcome and the Vitest diff names the failure.

♻️ Proposed change
+timeout_code = "no-error-raised"
 try:
     guard._run_state_dir_guard(
+transaction_error = {"code": "no-error-raised", "detail": ""}
 try:
     guard._run_failed_startup_unlock(
         object(), object(), guard.PRODUCTION_CONFIG_DIR, "{}", 91, quarantine_untrusted=False
     )
+timeout_transaction_code = "no-error-raised"
 try:
     guard._run_failed_startup_unlock(
         object(), object(), guard.PRODUCTION_CONFIG_DIR, "{}", 91, quarantine_untrusted=False
     )
 except guard.GuardError as error:
     timeout_transaction_code = error.code

Also applies to: 167-196

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` around lines 140 -
149, Initialize timeout_code, transaction_error, and timeout_transaction_code to
distinct sentinel values before their respective try blocks in the test harness.
Keep the existing GuardError assignments unchanged so successful exception paths
overwrite the sentinels, while missing exceptions produce a JSON payload and
Vitest diff that clearly report the unexpected outcome instead of raising
NameError.

132-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the os.path.isfile patch.

guard.os is the real os module, so this assignment replaces os.path.isfile for the whole interpreter, including subprocess, tempfile, and importlib. The patch is broader than the behavior under test. Scope it to the guard's own lookup, or restore it after the calls that need it.

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` at line 132, Replace
the global guard.os.path.isfile assignment with a scoped patch that affects only
the guard’s own lookup, or reliably restores the original function immediately
after the required calls. Keep the test’s forced-file-exists behavior while
preventing subprocess, tempfile, and importlib from observing the patched
os.path.isfile.

Source: Path instructions

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts`:
- Around line 140-149: Initialize timeout_code, transaction_error, and
timeout_transaction_code to distinct sentinel values before their respective try
blocks in the test harness. Keep the existing GuardError assignments unchanged
so successful exception paths overwrite the sentinels, while missing exceptions
produce a JSON payload and Vitest diff that clearly report the unexpected
outcome instead of raising NameError.
- Line 132: Replace the global guard.os.path.isfile assignment with a scoped
patch that affects only the guard’s own lookup, or reliably restores the
original function immediately after the required calls. Keep the test’s
forced-file-exists behavior while preventing subprocess, tempfile, and importlib
from observing the patched os.path.isfile.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8ae24f70-3173-4cd2-945b-a020f107bd20

📥 Commits

Reviewing files that changed from the base of the PR and between 431d6a2 and 65bb417.

📒 Files selected for processing (3)
  • scripts/openclaw-config-guard.py
  • src/lib/shields/index.ts
  • test/openclaw-config-guard-startup-failure-gate.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/shields/index.ts
  • scripts/openclaw-config-guard.py

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

CI classification for the current branch revision:

  • Every required check passes, along with ordinary CI, direct managed startup for all three supported agents, security scanning, documentation review, DCO, commit verification, CodeRabbit, and both advisor lanes.
  • The remaining managed runtime activation failure reports normalized Docker fields outside the declared deltas: AttachStderr, AttachStdout, and PortBindings. The same failure occurred on merged PR #8261, so this is inherited from main, not caused by the startup-recovery change.

No PR code change or rerun is indicated by this failure. The remaining repository gate is independent approval.

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Security review receipt for the current branch revision (c28ed2e63): WARNING — merge remains blocked on installed-sandbox boundary evidence.

Category Verdict Evidence
Secrets and credentials PASS No secrets, tokens, credential files, or new secret logging are introduced.
Input validation and sanitization PASS The new action is allowlisted, restricted to the production config path, uses the bounded state-lock-plan parser, authenticates the installed helpers, and fails closed on malformed or ambiguous process evidence.
Authentication and authorization WARNING The root-only recovery is limited to one action, requires a stable OpenShell supervisor with no startup child or readiness marker, and repeats the census while holding the mutation mutex. The actual installed namespace and privilege boundary has not yet been exercised for both childless success and live-child denial.
Dependencies and third-party libraries PASS No dependencies or external sources change.
Error handling and logging PASS Guard failures remain structured, subprocess output is bounded before surfacing, timeouts share one action budget, and rollback failures are reported rather than hidden.
Cryptography and data protection PASS No cryptographic behavior changes; existing state/config ownership and immutability protections are preserved.
Configuration and security defaults PASS The inherited lock descriptor must identify the private root-owned 0600 mutex, match the live path, and share the caller's exclusive lock. Separately opened descriptors are denied.
Security testing WARNING Synthetic procfs tests cover absent, live, duplicate, foreign, stale-marker, bounded-scan, and reconfirmation cases; the inherited-lock test covers accepted and refused descriptors. An installed-sandbox test for the privileged boundary is still missing.
Holistic security posture WARNING The implementation is fail-closed in reviewed unit and integration paths, but the new privileged recovery authority should not merge until its installed-sandbox childless-success and live-child-refusal behavior is demonstrated.

Reviewed files: scripts/openclaw-config-guard.py, scripts/state-dir-guard.py, src/lib/shields/index.ts, src/lib/shields/openclaw-config-lock.ts, src/lib/shields/openclaw-config-lock.test.ts, test/openclaw-config-guard-startup-failure-gate.test.ts, and docs/manage-sandboxes/recover-rebuild-sandboxes.mdx.

The existing documentation receipt check passes, and the documentation accurately describes the OpenClaw-only recovery flow. No additional documentation change is indicated by this review.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Maintainer follow-up on the current branch revision (f7ca3d1f8):

Review-warning disposition

The installed-sandbox evidence gap is addressed in test/e2e/live/shields-config.test.ts. The existing live Shields target now:

  • uses a source-installed OpenShell sandbox and the guards installed in its production image;
  • removes the readiness markers, proves the real supervised startup child is present, and verifies that unlock-failed-startup refuses that live-child posture;
  • terminates the startup child, waits for a stable childless census, and verifies that the installed recovery action unlocks the top-level config and recursive state tree with the installed state-lock plan;
  • reconciles the host Shields receipt, restarts the sandbox, and restores lockdown for cleanup.

Successful recursive unlock is also evidence that the state guard accepted the mutation-lock descriptor inherited from the config guard; a separately opened descriptor remains covered by the negative unit test.

Validation

  • 57 focused TypeScript Shields/guard tests passed.
  • The recovery census and rollback harnesses passed locally; the inherited-lock filesystem mutation case reaches the expected lock validation on macOS but cannot complete because the guard intentionally rejects the /var symlink used by the macOS temporary-directory path. Linux remains the authoritative runtime for that case.
  • CLI build and repository type checking passed.
  • E2E semantic phase validation passed (122 tests across 79 files).
  • Formatting, repository checks, secret scanning, source-shape budget, and test-size budget passed.
  • npm run docs passed; Fern reported its existing two non-fatal warnings.
  • Both new maintainer commits are signed off and appear as Verified in GitHub.

The live shields-config target loads and reaches the revised 13-phase plan locally, but this environment does not have the protected NVIDIA_INFERENCE_API_KEY, so it correctly skipped before creating a sandbox. The installed-sandbox behavior still needs a protected live run when GitHub Actions service capacity and the repository secret are available.

Security review

Category Result Evidence
Secrets and credentials PASS No new credential flow; the live target uses the existing redacting secret fixture.
Input validation and data sanitization PASS Fixed installed paths, bounded procfs census, validated plan input, and lock metadata/inode/mode/size checks.
Authentication and authorization PASS Root and installed-helper requirements, stable supervisor identity, childless-only authorization, and a mutex-held reconfirmation.
Dependencies and third-party libraries PASS No dependency or downloaded-artifact changes.
Error handling and logging PASS Security failures propagate; partial unlocks attempt to re-seal both layers within one deadline.
Cryptography and data protection PASS No cryptographic or transport changes.
Configuration and security headers PASS No permissive fallback for an older image; absence of the installed guard is a hard failure.
Security testing PASS, execution pending Unit coverage includes live, duplicate, foreign, stale-marker, bounded-scan, changing-state, timeout, rollback, and foreign-lock cases; the installed boundary test is checked in and awaits protected live infrastructure.
System security PASS Pre-effect reconfirmation, shared lock ownership, fixed process ancestry, fail-closed uncertainty, and bounded rollback preserve the whole transition.

No security finding requires a code change.

Independent documentation review approved docs/manage-sandboxes/recover-rebuild-sandboxes.mdx with no further edits. It correctly scopes the recovery, names the two admission conditions, retains the ordinary readiness requirement for other operations, and documents restart plus lockdown restoration.

Remaining blockers

  1. GitHub Actions is still affected by the service outage: the new CodeQL jobs are queued and the protected live sandbox run has not started. I did not dispatch repeated reruns.
  2. Current-main PR validation now rejects the earlier contributor commit 3b544e714 because its style commit type is no longer accepted. Correcting that published commit while preserving contributor verification cannot be done with a normal non-force push. This needs the contributor to publish compliant replacement history, or a repository-policy decision from a human maintainer.
  3. CodeRabbit's review of the refreshed branch is still in progress, and repository review approval remains required.

I am leaving the PR open and unmerged until those gates are resolved.

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

🧹 Nitpick comments (4)
test/e2e/live/shields-config.test.ts (1)

273-288: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce coupling to guard internals in the census probe.

_production_identity and _openshell_supervised_nonroot_start_census return the expected values, but runpy.run_path still binds this E2E test to private implementation details. Use a stable container liveness boundary, such as /proc, or expose a supported diagnostic boundary.

🤖 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 `@test/e2e/live/shields-config.test.ts` around lines 273 - 288, Update the
census probe around the script invoking runpy.run_path to stop importing and
calling the private guard symbols _production_identity and
_openshell_supervised_nonroot_start_census. Replace this with a stable supported
diagnostic boundary, preferably a /proc-based container liveness/process census,
while preserving the StartupCensus JSON shape and existing Docker execution
assertions.

Source: Path instructions

test/openclaw-config-guard-startup-failure-gate.test.ts (3)

244-247: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

The privilege boundary is not exercised: root_uid equals sandbox_uid here.

The child sets root_uid and sandbox_uid to the same os.getuid(). Every check that distinguishes the root layer from the sandbox layer therefore passes trivially. The PR review record lists this as the outstanding blocker: the installed-sandbox boundary has no coverage for childless recovery success and for live-child refusal.

The synthetic-/proc HARNESS covers live_recovery and reconfirm_child_appeared at the census level, but no test drives the installed helper with distinct root and sandbox uids.

Do you want me to draft the installed-sandbox test that separates the two uids and covers both outcomes?

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` around lines 244 -
247, Update the installed-sandbox test setup around guard.Identity to use
distinct root and sandbox UIDs, then add coverage for both childless recovery
success and refusal when a live child is present. Preserve the existing
synthetic-/proc census tests while exercising the installed helper across the
actual privilege boundary.

85-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert CLI exposure through the parser boundary, not parser._actions.

cli_accepts reads the argparse private attribute _actions. An argparse internal rename would break this test without any behavior change. Use parse_args instead, and keep the typing.get_args(guard.Action) cross-check.

♻️ Proposed refactor to use the public parser boundary
 def cli_accepts(action):
     parser = guard._parser()
-    choices = next(a.choices for a in parser._actions if a.dest == "action")
-    return action in set(choices) and set(choices) == set(typing.get_args(guard.Action))
+    try:
+        parsed = parser.parse_args([action, "--config-dir", guard.PRODUCTION_CONFIG_DIR])
+    except SystemExit:
+        return False
+    return parsed.action == action and parsed.plan_json is None

Then assert the Action literal coverage separately, for example by parsing every value of typing.get_args(guard.Action).

As per path instructions: "Prefer observable outcomes through the public boundary over source-text, private-shape, or mock-call assertions."

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` around lines 85 -
88, Update cli_accepts to stop inspecting the private parser._actions structure
and validate actions through guard._parser().parse_args instead. Preserve the
typing.get_args(guard.Action) cross-check by separately parsing every Action
literal value and confirming each is accepted by the CLI boundary.

Source: Path instructions


159-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an assertion that the rollback receives the remaining budget, not a fresh one.

Both fakes ignore _deadline. No test proves that _run_failed_startup_unlock shares one deadline between the unseal and the relock. A regression that gives the rollback a fresh STATE_DIR_GUARD_TIMEOUT_SECONDS would still pass this suite, and that regression is what makes the guard outlive the host budget.

Record the _deadline value in each fake and assert that the lock rollback deadline equals the unlock deadline.

♻️ Proposed addition to capture the shared deadline
+deadlines = []
+
 def state_dir(action, _config_dir, _plan_json, lock_fd, _deadline):
     assert lock_fd == 91
+    deadlines.append(_deadline)
     events.append(f"state-{action}")
     if action == "lock":
         raise guard.GuardError("state-lock-failed", guard.PRODUCTION_CONFIG_DIR, "lock failed")

Then export "shared_deadline": len(set(deadlines)) == 1 and assert it is true in the Vitest expectation.

As per path instructions: "Migration tests must prove the superseded path is unreachable or removed, not merely prove that the new path also works."

🤖 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 `@test/openclaw-config-guard-startup-failure-gate.test.ts` around lines 159 -
196, Update the test fakes used by _run_failed_startup_unlock to record each
_deadline received during the unseal and rollback transitions, then expose
whether all recorded deadlines are identical (for example, via the existing
result returned to the Vitest expectation). Assert that shared-deadline value is
true, proving the lock rollback reuses the remaining budget rather than creating
a fresh timeout.

Source: Path instructions

🤖 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.

Inline comments:
In `@scripts/openclaw-config-guard.py`:
- Around line 1576-1584: Update _transition to pass its quarantine_untrusted
argument through to _freeze. Ensure both the forward unlock path and
failed-startup rollback invoke _transition with the correct quarantine setting
so untrusted reserved entries are quarantined.

In `@test/e2e/live/shields-config.test.ts`:
- Around line 312-317: Update the termination step around the docker kill
invocation and the terminate.exitCode assertion to tolerate the startup process
exiting between the census and signal. Validate the subsequent census result as
the source of truth, or conditionally accept the known “no such process” exit
while preserving failures for unexpected kill errors.

---

Nitpick comments:
In `@test/e2e/live/shields-config.test.ts`:
- Around line 273-288: Update the census probe around the script invoking
runpy.run_path to stop importing and calling the private guard symbols
_production_identity and _openshell_supervised_nonroot_start_census. Replace
this with a stable supported diagnostic boundary, preferably a /proc-based
container liveness/process census, while preserving the StartupCensus JSON shape
and existing Docker execution assertions.

In `@test/openclaw-config-guard-startup-failure-gate.test.ts`:
- Around line 244-247: Update the installed-sandbox test setup around
guard.Identity to use distinct root and sandbox UIDs, then add coverage for both
childless recovery success and refusal when a live child is present. Preserve
the existing synthetic-/proc census tests while exercising the installed helper
across the actual privilege boundary.
- Around line 85-88: Update cli_accepts to stop inspecting the private
parser._actions structure and validate actions through
guard._parser().parse_args instead. Preserve the typing.get_args(guard.Action)
cross-check by separately parsing every Action literal value and confirming each
is accepted by the CLI boundary.
- Around line 159-196: Update the test fakes used by _run_failed_startup_unlock
to record each _deadline received during the unseal and rollback transitions,
then expose whether all recorded deadlines are identical (for example, via the
existing result returned to the Vitest expectation). Assert that shared-deadline
value is true, proving the lock rollback reuses the remaining budget rather than
creating a fresh timeout.
🪄 Autofix

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: Enterprise

Run ID: e32d1f6c-f0d5-4328-a60c-2e1e8c209a0f

📥 Commits

Reviewing files that changed from the base of the PR and between affe641 and f7ca3d1.

📒 Files selected for processing (8)
  • docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
  • scripts/openclaw-config-guard.py
  • scripts/state-dir-guard.py
  • src/lib/shields/index.ts
  • src/lib/shields/openclaw-config-lock.test.ts
  • src/lib/shields/openclaw-config-lock.ts
  • test/e2e/live/shields-config.test.ts
  • test/openclaw-config-guard-startup-failure-gate.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/manage-sandboxes/recover-rebuild-sandboxes.mdx
  • scripts/state-dir-guard.py
  • src/lib/shields/openclaw-config-lock.test.ts
  • src/lib/shields/index.ts
  • src/lib/shields/openclaw-config-lock.ts

Comment thread scripts/openclaw-config-guard.py
Comment thread test/e2e/live/shields-config.test.ts
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Superseded by #8488. The replacement preserves Rui Luo as the implementation author, retains Rui and Apurv DCO declarations, and all three replacement commits appear as GitHub Verified. It first reproduced this branch result on current main, then incorporated the two final actionable review findings: quarantine intent is preserved through forward recovery and rollback, and the live signal race accepts only the known already-exited result while the child census remains authoritative. Both original review threads link to the corresponding replacement evidence, and #8488 carries the full validation, documentation, and nine-category security-review receipt. Closing this PR only because #8488 is now the documented replacement.

cv added a commit that referenced this pull request Aug 7, 2026
…8488)

<!-- markdownlint-disable MD041 -->
## Summary
This PR supersedes [#8460](#8460)
with a clean Conventional Commits history while preserving Rui Luo's
authorship and DCO declaration. It keeps the reviewed recovery behavior
and incorporates all actionable review feedback: a shields-up OpenClaw
sandbox whose startup terminally failed can lower shields without a
destructive rebuild, but only after the installed guard proves there is
no live startup child or readiness lease.

## Related Issue
Fixes #8304

Supersedes [#8460](#8460).

## Changes
- Preserve Rui Luo's recovery implementation and documentation as one
contributor-authored, dual-DCO commit based on current `main`. The
replacement first reproduced #8460's branch result; later commits
preserve quarantine intent and address the completed review feedback
with regression coverage.
- Extract the supervised-start procfs scan into a stable census that
fails closed for foreign, duplicate, changing, or undeterminable process
topology.
- Add the installed-only `unlock-failed-startup` action. It reconfirms
childless failed startup under the config mutation mutex, passes the
held descriptor to the installed state guard, atomically unseals both
layers, and attempts to re-seal both layers after partial failure.
- Route `shields down` through the ordinary read-only preflight first,
use failed-startup recovery only for the readiness refusal, and surface
every other transition, contract, parse, timeout, or rollback error.
- Keep a ten-minute forward budget and a separate twelve-minute rollback
reserve inside a twenty-five-minute container limit, while the host
remains attached for twenty-six minutes; refuse recovery when the
installed helper is unavailable.
- Document the OpenClaw recovery sequence before rebuild guidance.
- Extend the live Shields sandbox target to prove the installed guard
refuses a live supervised startup child, accepts stable childless
recovery with the installed state plan and inherited mutation lock,
restarts successfully, and restores lockdown.
- Preserve the untrusted-journal quarantine setting through forward
recovery, transition freeze, and rollback; assert each handoff in the
transaction regression harness.
- Treat only the known already-exited process result as acceptable when
the live target signals the startup child, while retaining the
subsequent child census as the success authority and rejecting
unexpected signal failures.

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [x] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Quality Gates
<!-- Check one tests line and one docs line. Check other lines when
applicable. Add every requested justification or approval reference. -->
- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [x] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: nine-category
maintainer security review found no code issue; receipt:
#8488 (comment)
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Documentation Writer Review
<!-- Required for code and documentation changes after the changes and
applicable validation are complete. Keep one review checkbox and one
instance of each visible or hidden field. For Evidence, list changed
documentation paths. For documentation-only changes, also state that the
writing rules and documentation style were reviewed. For other results,
explain why no documentation change is needed or why the review is
blocked. For Agent, use a consistent product and surface name, such as
Codex Desktop, Codex CLI, Claude Code, or Cursor. After committing all
review changes, put `git rev-parse --short HEAD` and `git rev-parse
--short HEAD:AGENTS.md` in the hidden metadata below. Rerun the review
and refresh that metadata after any new commit. This receipt is advisory
during the data-collection pilot. -->
- [x] Documentation writer subagent reviewed the completed changes
- Result: `docs-updated`
- Evidence: `docs/manage-sandboxes/recover-rebuild-sandboxes.mdx`;
independent review confirmed that the OpenClaw-only recovery guidance
matches the guarded failed-startup unlock, states the process and
readiness checks precisely, names the required config and state guards,
and separates the restart and Shields restoration steps. `npm run docs`
passed with 0 errors and the 2 existing non-fatal Fern warnings.
- Agent: Codex Desktop
<!-- docs-review-head-sha: e7aecc8 -->
<!-- docs-review-agents-blob-sha: c69aad4 -->

## DGX Station Hardware Evidence
<!-- Required only when scripts/prepare-dgx-station-host.sh changes.
Maintainers must review the linked evidence before approving or merging.
This is human-reviewed evidence, not authenticated hardware provenance.
Exceptional bypasses use existing repository governance and must be
documented on the PR. -->
- [ ] Tested on DGX Station
- Tested commit:
- Station profile/scenario:
- Result:
- Supporting evidence:

## Verification
<!-- Check each applicable item only when supported by the requested
evidence. Run targeted tests once per relevant change set and rerun
after later edits or hook autofixes that can affect the tested behavior.
Do not rerun hook-covered checks. -->
- [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 validate:pr` passed after refreshing `origin/main` 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: 68
focused CLI Shields/guard tests and 4 dedicated failed-startup
transaction/classification tests passed on the current branch revision.
CLI build, CLI type checking, repository integrity checks, Python
compilation, full PR validation, and documentation validation also
passed. The installed live target loaded and skipped before sandbox
creation because the protected inference secret is unavailable locally.
- [ ] 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)
- [ ] 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)

---
<!-- DCO sign-off is required in this PR description, and every commit
must appear as Verified in GitHub. Run: git config user.name && git
config user.email -->
Signed-off-by: Rui Luo <ruluo@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>


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

- **New Features**
- Added automatic recovery for sandboxes that remain unavailable after a
failed startup.
- Safely unlocks configuration and workspace state only after startup
processes stop, then restores protections.
- Preserves existing safeguards and reports specific recovery issues
when conditions are not met.

- **Documentation**
- Added guidance for using `shields down` when restarting does not
restore gateway availability.
- Clarified readiness checks, required sandbox support, upgrade/rebuild
steps, and when to restart before restoring shields.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Co-authored-by: Rui Luo <ruluo@nvidia.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants