Skip to content

fix(shields): self-heal root-owned mutable OpenClaw config on boot#6307

Merged
apurvvkumaria merged 13 commits into
mainfrom
fix/openclaw-config-perms-self-heal
Jul 7, 2026
Merged

fix(shields): self-heal root-owned mutable OpenClaw config on boot#6307
apurvvkumaria merged 13 commits into
mainfrom
fix/openclaw-config-perms-self-heal

Conversation

@laitingsheng

@laitingsheng laitingsheng commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Self-heal the conservative root-owned OpenClaw mutable-config signature before transaction recovery can mistake it for an interrupted shields transition. The repair restores sandbox writes in the default state while sealed, ambiguous, linked, raced, and mounted trees remain fail-closed.

This remains Tinson Lai's original PR and branch. His six commits and PR authorship are retained; the maintainer follow-up hardens and verifies that implementation.

Related Issue

Fixes #6300

Preserves the repair and hardlink-safety contract from #6047 / #6060.

Changes

  • Classify the complete recovery boundary: sandbox:sandbox 0755 parent, root:root 0700 config directory, and single-link root:root 0600 openclaw.json and .config-hash.
  • Run only that exact recovery before config-transaction recovery; leave sealed and indeterminate states to the guard.
  • Pin the parent, directory, and fixed files with no-follow descriptors; require stable metadata, same device, and same Linux mount ID.
  • Reclaim only the directory and two fixed files, handing the directory to the sandbox identity last instead of recursively changing unrelated state.
  • Return a failure for missing, unsafe, ambiguous, or non-root-unrepairable mutable states.
  • Add host and production-image coverage for startup ordering, real sandbox ownership/writes, baseline preservation, sealed and ambiguous postures, parent mismatch, hardlinks, symlinks, path disappearance, mounts, and missing identity capabilities.
  • Document the recovery/fail-closed behavior and privileged normalizer TCB boundary.

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: independent nine-category security re-review found no blocking issues; implementation and evidence are summarized in the final-head maintainer disposition.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npx prek run --from-ref main --to-ref HEAD passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result: focused integration 24 passed with 3 intentional root-only host skips; final-head production-image gateway/isolation 45 passed, 0 failed
  • 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) — 0 errors; Fern reported 2 pre-existing hidden warnings
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Evidence

  • npx vitest run --project integration test/nemoclaw-start-perms.test.ts test/repro-2681-group-writable.test.ts: 24 passed, 3 intentional root-only host skips.
  • NEMOCLAW_TEST_IMAGE=nemoclaw-pr6307-test-v2 bash test/e2e-gateway-isolation.sh: 45 passed, 0 failed.
  • npx prek run --from-ref origin/main --to-ref HEAD: passed.
  • npm run docs: 0 errors; 2 pre-existing hidden Fern warnings.
  • All seven maintainer commits are SSH-signed, include Signed-off-by, and report GitHub verification { "verified": true, "reason": "valid" }.
  • Final-head security and review-feedback re-audits have no blocking findings.
  • Final-head required CI and exact-head live shields-config run 28890370745 passed.

Signed-off-by: Tinson Lai tinsonl@nvidia.com
Signed-off-by: Apurv Kumaria akumaria@nvidia.com

Summary by CodeRabbit

  • New Features

    • Enhanced startup flow for mutable config permission recovery, including seal classification and safe reclaim when the config tree begins root-owned.
    • Added trusted helper support to ensure normalization runs with consistent resolution logic before recovery.
  • Bug Fixes

    • Tightened fail-closed behavior for ambiguous or unsafe mutable config layouts (including ownership, mode posture, links, and mount identity).
    • Improved handling so only correctly sealed, descriptor-pinned root-owned trees are reclaimed.
  • Tests

    • Expanded permission/startup/reclaim coverage with new seal classification and ordering scenarios.
  • Documentation

    • Updated troubleshooting guidance and security boundary details to match the stricter behavior.

The boot-time normalize step treated any root-owned /sandbox/.openclaw as
an intentional shields-up lock and skipped repair. When the runtime leaves
the mutable-default config tree root-owned, the sandbox user can no longer
write openclaw.json in the default state.

Classify a root-owned tree by its sealed signature (0755 dir plus a 0444
root:root openclaw.json) and reclaim only a confirmed collapse back to the
sandbox:sandbox 2770/660 group contract. A genuine shields-up lock, or any
ambiguous state, is left untouched so the lock is never weakened. The
reclaim is root-only and refuses symlinked config paths.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 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

Adds seal classification and reclaim logic for root-owned OpenClaw config trees, wires startup to use it, updates shell test harnesses and coverage, and documents the new trusted-helper boundaries.

Changes

Mutable config seal/reclaim

Layer / File(s) Summary
Normalize flow branches on seal state
scripts/nemoclaw-start.sh
normalize_mutable_config_perms now resolves the trusted normalizer helper, returns unchanged for non-normalize operations on root-owned config directories, and invokes reclaim during normalize when the UID probe reports root ownership.
Seal classifier and reclaim helpers
scripts/lib/normalize_mutable_config_perms.py
Adds seal-state constants, fixed-file posture checks, pinned config binding, seal classification, and descriptor-pinned reclaim logic with new CLI subcommands.
Startup recovery orchestration
scripts/nemoclaw-start.sh
Wraps the root-mode startup sequence in prepare_openclaw_config_startup, which revokes readiness, classifies and optionally reclaims the mutable config tree, runs startup-owner recovery, and resumes the interrupted recursive state lock; the PID 1 path now calls this helper directly.
Test harness and flow coverage
test/e2e-gateway-isolation.sh, test/nemoclaw-start.test.ts, test/nemoclaw-start-perms.test.ts, test/repro-2681-group-writable.test.ts
Updates shell harness extraction to include the resolver and new helpers, adds stricter replacement utilities, and expands coverage for seal classification, reclaim behavior, startup ordering, and e2e cleanup/recovery edge cases.
Docs updates
docs/reference/troubleshooting.mdx, docs/security/tcb-boundary.mdx
Updates troubleshooting guidance and the security TCB boundary table to describe the tightened ownership-reclaim conditions and the trusted normalizer helper.

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

Possibly related PRs

  • NVIDIA/NemoClaw#2851: Also modifies scripts/nemoclaw-start.sh mutable-config permission normalization and reclaim behavior.
  • NVIDIA/NemoClaw#2864: Also updates the mutable-config permission test harness in test/repro-2681-group-writable.test.ts.

Suggested reviewers: jyaunches, cjagwani

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes restore writable sandbox-owned mutable config at boot while preserving sealed and unsafe states as fail-closed, matching #6300.
Out of Scope Changes check ✅ Passed The added tests and docs stay focused on the boot-time mutable-config repair and its security boundary.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: boot-time self-healing of a root-owned mutable OpenClaw config.
✨ 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/openclaw-config-perms-self-heal

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

@github-code-quality

github-code-quality Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the branch is 96%. Coverage data for the branch is not yet available.

Show a code coverage summary of the most covered files.
File c73ef23 +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the branch is 75%. Coverage data for the branch is not yet available.

Show a code coverage summary of the most covered files.
File c73ef23 +/-
src/lib/shields...nsition-lock.ts 85%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 80%
src/lib/actions...licy-channel.ts 79%
src/lib/state/sandbox.ts 75%
src/lib/onboard...er-gpu-patch.ts 69%
src/lib/policy/index.ts 66%
src/lib/shields/index.ts 61%
src/lib/onboard.ts 28%

Updated July 07, 2026 18:53 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-1: Concurrent reclaim race test missing; then add or justify PRA-T1.
Open items: 3 required · 11 warnings · 5 suggestions · 8 test follow-ups
Since last review: 3 prior items resolved · 7 still apply · 4 new items found

Action checklist

  • PRA-1 Fix: Concurrent reclaim race test missing in test/e2e-gateway-isolation.sh:539
  • PRA-2 Fix: Concurrent capture vs reclaim test missing in test/e2e-gateway-isolation.sh:539
  • PRA-3 Fix: Root-owned config reclaim is a workaround for unidentified regression [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 in scripts/nemoclaw-start.sh:586
  • PRA-4 Resolve or justify: Startup ordering workaround for orphan-freeze discriminator overlap in scripts/nemoclaw-start.sh:782
  • PRA-5 Resolve or justify: Source-of-truth review needed for root-owned config reclaim ([All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 workaround) in scripts/nemoclaw-start.sh:586
  • PRA-6 Resolve or justify: fd_mount_id() Linux-only restriction undocumented in scripts/lib/normalize_mutable_config_perms.py:150
  • PRA-7 Resolve or justify: Non-root env helper fallback rationale missing per-branch comments in scripts/nemoclaw-start.sh:515
  • PRA-8 Resolve or justify: Socketpair SCM_RIGHTS credential passing untested at unit level in test/nemoclaw-start-perms.test.ts:450
  • PRA-9 Resolve or justify: Reclaim intermediate states not crash-tested in scripts/lib/normalize_mutable_config_perms.py:380
  • PRA-10 Resolve or justify: fd_mount_id() arbitrary 4096 byte read limit in scripts/lib/normalize_mutable_config_perms.py:162
  • PRA-11 Resolve or justify: fd_mount_id() fails closed on non-Linux with no graceful degradation for test environments in scripts/lib/normalize_mutable_config_perms.py:150
  • PRA-12 Resolve or justify: Overlapping open PRs modify nemoclaw-start.sh and nemoclaw-start.test.ts in test/e2e-gateway-isolation.sh:100
  • PRA-13 Resolve or justify: classify_config_path() breaks non-Linux non-root paths via fd_mount_id() in scripts/lib/normalize_mutable_config_perms.py:230
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Runtime validation
  • PRA-T5 Add or justify test follow-up: Runtime validation
  • PRA-T6 Add or justify test follow-up: Concurrent reclaim race test missing
  • PRA-T7 Add or justify test follow-up: Concurrent capture vs reclaim test missing
  • PRA-T8 Add or justify test follow-up: Socketpair SCM_RIGHTS credential passing untested at unit level
  • PRA-15 In-scope improvement: resolve_mutable_config_normalizer() can be simplified to 2 paths in scripts/nemoclaw-start.sh:510
  • PRA-16 In-scope improvement: Extract shared e2e test helper to avoid sed duplication in test/e2e-gateway-isolation.sh:539
  • PRA-17 In-scope improvement: resolve_mutable_config_normalizer() has redundant repo-relative and script-dir-relative paths in scripts/nemoclaw-start.sh:510
  • PRA-18 In-scope improvement: Documentation for root config recovery boundary missing test 30g reference in docs/reference/troubleshooting.mdx:1300
  • PRA-19 In-scope improvement: Test 30g monkey-patches python3 shell function - fragile TOCTOU test in test/e2e-gateway-isolation.sh:1100

Findings index

ID Severity Category Location Required action
PRA-1 Required tests test/e2e-gateway-isolation.sh:539 Add an e2e test spawning N parallel reclaim_if_unsealed processes on the same config dir; verify exactly one succeeds and the tree ends in 2770/660 sandbox:sandbox ownership.
PRA-2 Required tests test/e2e-gateway-isolation.sh:539 Add an e2e test running write_openclaw_config_baseline in a loop while reclaim executes; verify no corruption, baseline ends root:440, config tree ends 2770/660.
PRA-3 Required security scripts/nemoclaw-start.sh:586 File a tracking issue for #6300 root cause. The fail-closed design and comprehensive test 30g make this acceptable for now, but the workaround must not become permanent.
PRA-4 Resolve/justify architecture scripts/nemoclaw-start.sh:782 Identify why the transaction guard's orphan-freeze discriminator overlaps with the #6300 reclaim signature, fix the discriminator at its source in openclaw-config-guard.py, or document why the overlap cannot be resolved upstream with a removal condition.
PRA-5 Resolve/justify architecture scripts/nemoclaw-start.sh:586 Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition. File a tracking issue for the root cause.
PRA-6 Resolve/justify docs scripts/lib/normalize_mutable_config_perms.py:150 Add comment to fd_mount_id() explaining Linux-only restriction is intentional for production containers; test environments should use the non-root fallback path which doesn't require mount ID verification.
PRA-7 Resolve/justify docs scripts/nemoclaw-start.sh:515 Add inline comments at each fallback branch (NEMOCLAW_MUTABLE_CONFIG_NORMALIZER, repo-relative, script-dir-relative) explicitly stating 'dev/test only — cannot change ownership in production'.
PRA-8 Resolve/justify tests test/nemoclaw-start-perms.test.ts:450 Add a focused unit test for the receive_owner_fd / child socket protocol, or document that e2e tests 30-30h are the authoritative coverage for the privilege boundary.
PRA-9 Resolve/justify correctness scripts/lib/normalize_mutable_config_perms.py:380 Add a test simulating crash between reclaim steps (e.g., SIGKILL after fixed files reclaimed but before directory handoff) and verify startup idempotently recovers or fails closed.
PRA-10 Resolve/justify correctness scripts/lib/normalize_mutable_config_perms.py:162 Either increase buffer with documented rationale, or read in a loop until EOF. Document the limit if kept.
PRA-11 Resolve/justify security scripts/lib/normalize_mutable_config_perms.py:150 Ensure the non-root fallback path in normalize_mutable_config_perms() is exercised on non-Linux platforms, or document that mount ID verification is Linux-only and non-root tests must not require it.
PRA-12 Resolve/justify tests test/e2e-gateway-isolation.sh:100 Coordinate with maintainers to sequence merges or rebase this PR after conflicting PRs land. Verify no semantic conflicts in normalize_mutable_config_perms resolution and startup ordering.
PRA-13 Resolve/justify security scripts/lib/normalize_mutable_config_perms.py:230 Verify non-root path in normalize_mutable_config_perms() avoids classify_config_path() on non-Linux, or add a platform guard. The non-root path calls normalize_owner_tree() which doesn't use classify_config_path(), but classify_openclaw_config_seal() (used by startup) does.
PRA-14 Resolve/justify correctness scripts/lib/normalize_mutable_config_perms.py:450 Add crash test for lock_recovery_baseline intermediate states (SIGKILL after os.replace but before fsync).
PRA-15 Improvement correctness scripts/nemoclaw-start.sh:510 Simplify to 2 paths (installed + NEMOCLAW_MUTABLE_CONFIG_NORMALIZER for dev) and verify all test scenarios still pass.
PRA-16 Improvement architecture test/e2e-gateway-isolation.sh:539 Extract common test setup into a sourced helper script; verify all 9 test variants still pass.
PRA-17 Improvement correctness scripts/nemoclaw-start.sh:510 Remove one of the two checkout-relative fallback paths to reduce attack surface and complexity.
PRA-18 Improvement correctness docs/reference/troubleshooting.mdx:1300 Add a reference to test 30g (ROOT_BOOT_RECLAIM_OK) in the troubleshooting documentation.
PRA-19 Improvement correctness test/e2e-gateway-isolation.sh:1100 Consider a more robust race test using actual concurrent processes, or document why the shell function override is the appropriate test strategy for this TOCTOU.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-1 Required — Concurrent reclaim race test missing

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: tests
  • Problem: The Python normalizer uses descriptor pinning to serialize reclaim_if_unsealed but no test validates concurrent callers on the same config directory. Two processes calling reclaim_if_unsealed simultaneously could race, potentially corrupting the config tree or leaving it in an inconsistent state, breaking the sandbox:sandbox 2770/660 contract.
  • Impact: If a race condition exists, one reclaim could corrupt the config tree or leave it in an inconsistent state, breaking the sandbox:sandbox 2770/660 contract and causing sandbox/gateway write failures.
  • Required action: Add an e2e test spawning N parallel reclaim_if_unsealed processes on the same config dir; verify exactly one succeeds and the tree ends in 2770/660 sandbox:sandbox ownership.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/e2e-gateway-isolation.sh for 'concurrent reclaim' — no such test exists.
  • Missing regression test: Concurrent reclaim race test covering parallel mutation attempts on the same config directory with pinned descriptors.
  • Done when: The required change is committed and verification passes: Search test/e2e-gateway-isolation.sh for 'concurrent reclaim' — no such test exists.
  • Evidence: reclaim_if_unsealed opens config dir once with O_NOFOLLOW and classifies+reclaims on same pinned descriptor (normalize_mutable_config_perms.py:260-340), but no test validates concurrent callers.

PRA-2 Required — Concurrent capture vs reclaim test missing

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: tests
  • Problem: Both write_openclaw_config_baseline (capture) and reclaim_collapsed_mutable_config operate on the config tree with pinned descriptors. Their interaction under concurrency is unverified — if capture runs during reclaim, the baseline could be written from a partially-reclaimed tree or the reclaim could see a partially-captured baseline.
  • Impact: Baseline corruption or inconsistent permissions; config tree could end in wrong ownership/mode state, breaking gateway writes or shields recovery.
  • Required action: Add an e2e test running write_openclaw_config_baseline in a loop while reclaim executes; verify no corruption, baseline ends root:440, config tree ends 2770/660.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/e2e-gateway-isolation.sh for 'concurrent capture' or 'capture.*reclaim' — no such test exists.
  • Missing regression test: Concurrent capture vs reclaim test covering overlapping pinned-descriptor operations.
  • Done when: The required change is committed and verification passes: Search test/e2e-gateway-isolation.sh for 'concurrent capture' or 'capture.*reclaim' — no such test exists.
  • Evidence: Both capture_recovery_baseline (line 1050+) and reclaim_if_unsealed (line 260+) use pinned descriptors but no test covers their concurrent execution.

PRA-3 Required — Root-owned config reclaim is a workaround for unidentified regression #6300

Review findings by urgency: 3 required fixes, 11 items to resolve/justify, 5 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-4 Resolve/justify — Startup ordering workaround for orphan-freeze discriminator overlap

PRA-5 Resolve/justify — Source-of-truth review needed for root-owned config reclaim (#6300 workaround)

PRA-6 Resolve/justify — fd_mount_id() Linux-only restriction undocumented

  • Location: scripts/lib/normalize_mutable_config_perms.py:150
  • Category: docs
  • Problem: fd_mount_id() fails closed on non-Linux (raises UnsafeTree) with no documentation that this is intentional for production containers. The classify_config_path() calls fd_mount_id() which will break non-root fallback paths on macOS/non-Linux test environments.
  • Impact: Non-Linux test environments (macOS CI) may fail seal classification unexpectedly, causing false negatives in config normalization.
  • Recommended action: Add comment to fd_mount_id() explaining Linux-only restriction is intentional for production containers; test environments should use the non-root fallback path which doesn't require mount ID verification.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read fd_mount_id() at line 150 — raises UnsafeTree on non-Linux with no explanatory comment.
  • Missing regression test: Verify non-root fallback path in normalize_mutable_config_perms() is exercised on non-Linux platforms.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read fd_mount_id() at line 150 — raises UnsafeTree on non-Linux with no explanatory comment.
  • Evidence: fd_mount_id() line 150-170: if not sys.platform.startswith('linux'): raise UnsafeTree()

PRA-7 Resolve/justify — Non-root env helper fallback rationale missing per-branch comments

  • Location: scripts/nemoclaw-start.sh:515
  • Category: docs
  • Problem: Non-root env helper fallback rationale should explicitly note dev-only scope at each fallback branch. Current comment says 'exist solely for non-root developer/test harnesses' but could be more explicit at each branch.
  • Impact: Developers may not realize the env/checkout fallbacks are dev-only and cannot change ownership in production, leading to confusion about security boundary.
  • Recommended action: Add inline comments at each fallback branch (NEMOCLAW_MUTABLE_CONFIG_NORMALIZER, repo-relative, script-dir-relative) explicitly stating 'dev/test only — cannot change ownership in production'.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Lines 515-540: comment at top of function but not at each branch.
  • Missing regression test: N/A — documentation improvement.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Lines 515-540: comment at top of function but not at each branch.
  • Evidence: resolve_mutable_config_normalizer() has 4 fallback paths with only top-level dev-only comment.

PRA-8 Resolve/justify — Socketpair SCM_RIGHTS credential passing untested at unit level

  • Location: test/nemoclaw-start-perms.test.ts:450
  • Category: tests
  • Problem: Socketpair SCM_RIGHTS credential passing (receive_owner_fd / child socket protocol) untested at unit level. The protocol transfers pinned directory descriptors and validates credentials via SCM_CREDENTIALS, but no focused unit test exercises this privilege boundary.
  • Impact: Privilege boundary validation relies solely on e2e tests; a regression in credential validation could go undetected until integration testing.
  • Recommended action: Add a focused unit test for the receive_owner_fd / child socket protocol, or document that e2e tests 30-30h are the authoritative coverage for the privilege boundary.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Search test/nemoclaw-start-perms.test.ts for 'receive_owner_fd' or 'SCM_RIGHTS' — no unit test found.
  • Missing regression test: Unit test for socketpair SCM_RIGHTS credential passing and descriptor transfer.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Search test/nemoclaw-start-perms.test.ts for 'receive_owner_fd' or 'SCM_RIGHTS' — no unit test found.
  • Evidence: receive_owner_fd() validates credentials == [(child_pid, expected_uid, expected_gid)] but no unit test exercises this.

PRA-9 Resolve/justify — Reclaim intermediate states not crash-tested

  • Location: scripts/lib/normalize_mutable_config_perms.py:380
  • Category: correctness
  • Problem: reclaim_if_unsealed() has multiple steps (open fixed files, reclaim fixed files, fchmod/fchown/fchmod directory handoff). A crash (SIGKILL) between steps could leave the tree in an inconsistent state.
  • Impact: If the process is killed between reclaiming fixed files and handing off the directory, the config tree could be left with sandbox-owned files but root-owned directory, breaking the 2770/660 contract.
  • Recommended action: Add a test simulating crash between reclaim steps (e.g., SIGKILL after fixed files reclaimed but before directory handoff) and verify startup idempotently recovers or fails closed.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Test 30g tests a race at classify time (monkey-patched python3) but not intermediate reclaim steps.
  • Missing regression test: Crash recovery test for reclaim intermediate states.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Test 30g tests a race at classify time (monkey-patched python3) but not intermediate reclaim steps.
  • Evidence: reclaim_if_unsealed() lines 380-420: multiple fchown/fchmod/fchmod steps with no crash recovery verification.

PRA-10 Resolve/justify — fd_mount_id() arbitrary 4096 byte read limit

  • Location: scripts/lib/normalize_mutable_config_perms.py:162
  • Category: correctness
  • Problem: fd_mount_id() reads exactly 4097 bytes and fails if payload > 4096. This arbitrary limit could truncate on systems with many mounts (/proc/self/fdinfo/N can exceed 4KB).
  • Impact: On systems with many mount points, the fdinfo read could exceed 4096 bytes, causing false UnsafeTree failures.
  • Recommended action: Either increase buffer with documented rationale, or read in a loop until EOF. Document the limit if kept.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 162: payload = os.read(fdinfo_fd, 4097); if len(payload) > 4096: raise UnsafeTree()
  • Missing regression test: Test fd_mount_id() with synthetic /proc/self/fdinfo content exceeding 4096 bytes.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 162: payload = os.read(fdinfo_fd, 4097); if len(payload) > 4096: raise UnsafeTree().
  • Evidence: fd_mount_id() uses fixed 4097 byte read with hard fail on overflow.

PRA-11 Resolve/justify — fd_mount_id() fails closed on non-Linux with no graceful degradation for test environments

  • Location: scripts/lib/normalize_mutable_config_perms.py:150
  • Category: security
  • Problem: fd_mount_id() fails closed on non-Linux with no graceful degradation for test environments. classify_config_path() calls fd_mount_id() which raises UnsafeTree on non-Linux, making seal classification fail on macOS CI runners unless the non-root fallback path avoids this code.
  • Impact: Non-Linux CI runners (macOS) cannot run seal classification, potentially missing regressions in the classify path.
  • Recommended action: Ensure the non-root fallback path in normalize_mutable_config_perms() is exercised on non-Linux platforms, or document that mount ID verification is Linux-only and non-root tests must not require it.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: classify_config_path() line 230 calls fd_mount_id() for both root and parent fd — will fail on non-Linux.
  • Missing regression test: CI test on non-Linux platform exercising non-root config normalization.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: classify_config_path() line 230 calls fd_mount_id() for both root and parent fd — will fail on non-Linux.
  • Evidence: fd_mount_id() raises UnsafeTree on non-Linux; classify_config_path() calls it for both root_fd and parent_fd.

PRA-12 Resolve/justify — Overlapping open PRs modify nemoclaw-start.sh and nemoclaw-start.test.ts

PRA-13 Resolve/justify — classify_config_path() breaks non-Linux non-root paths via fd_mount_id()

  • Location: scripts/lib/normalize_mutable_config_perms.py:230
  • Category: security
  • Problem: classify_config_path() calls fd_mount_id() for both root_fd and parent_fd on non-root paths. On non-Linux, this will raise UnsafeTree and return INDETERMINATE, potentially breaking non-root config normalization on macOS CI.
  • Impact: classify_openclaw_config_seal() (used by startup) calls classify_config_path() which calls fd_mount_id(). On macOS, this fails closed even for valid mutable configs.
  • Recommended action: Verify non-root path in normalize_mutable_config_perms() avoids classify_config_path() on non-Linux, or add a platform guard. The non-root path calls normalize_owner_tree() which doesn't use classify_config_path(), but classify_openclaw_config_seal() (used by startup) does.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: classify_openclaw_config_seal() calls classify_config_path() which calls fd_mount_id(). Test on non-Linux runner.
  • Missing regression test: Non-Linux CI test for classify_openclaw_config_seal().
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: classify_openclaw_config_seal() calls classify_config_path() which calls fd_mount_id(). Test on non-Linux runner.
  • Evidence: classify_config_path() line 230: fd_mount_id(root_fd) != fd_mount_id(parent_fd) — called by classify_openclaw_config_seal() used in prepare_openclaw_config_startup().

PRA-14 Resolve/justify — lock_recovery_baseline() crash between os.replace and fsync

  • Location: scripts/lib/normalize_mutable_config_perms.py:450
  • Category: correctness
  • Problem: lock_recovery_baseline() has complex temp-file staging (temp_name, temp_fd, temp_identity) with cleanup in finally block. A crash between os.replace() and os.fsync() could leave temp files or an inconsistent baseline state.
  • Impact: If process dies after os.replace but before fsync, the baseline file may not be durably written, causing recovery to fail or use stale baseline on next startup.
  • Recommended action: Add crash test for lock_recovery_baseline intermediate states (SIGKILL after os.replace but before fsync).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: lock_recovery_baseline() lines 450-550: multiple os.replace/fsync steps with temp file cleanup in finally.
  • Missing regression test: Crash recovery test for baseline locking intermediate states.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: lock_recovery_baseline() lines 450-550: multiple os.replace/fsync steps with temp file cleanup in finally.
  • Evidence: lock_recovery_baseline() uses os.replace then os.fsync with temp file cleanup in finally block.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-15 Improvement — resolve_mutable_config_normalizer() can be simplified to 2 paths

  • Location: scripts/nemoclaw-start.sh:510
  • Category: correctness
  • Problem: resolve_mutable_config_normalizer() has 4 resolution paths (installed, NEMOCLAW_MUTABLE_CONFIG_NORMALIZER env, repo-relative, script-dir-relative). Can simplify to 2 paths (installed + env for dev) since repo-relative and script-dir-relative are redundant for non-root test harnesses.
  • Impact: Unnecessary complexity and attack surface in normalizer resolution; two checkout-relative paths serve the same dev-only purpose.
  • Suggested action: Simplify to 2 paths (installed + NEMOCLAW_MUTABLE_CONFIG_NORMALIZER for dev) and verify all test scenarios still pass.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 510-540 show 4 fallback paths; repo-relative and script-dir-relative serve same purpose.
  • Missing regression test: Verify all existing test scenarios pass with simplified resolver.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: resolve_mutable_config_normalizer() has installed path, env var, repo-relative, and script-dir-relative fallbacks.

PRA-16 Improvement — Extract shared e2e test helper to avoid sed duplication

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: architecture
  • Problem: Each e2e test (30-30h) independently extracts shell functions via sed from /usr/local/bin/nemoclaw-start. This duplicates ~50 lines of extraction boilerplate per test.
  • Impact: Maintenance burden; changes to function signatures require updating 9+ test blocks; risk of extraction drift.
  • Suggested action: Extract common test setup into a sourced helper script; verify all 9 test variants still pass.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Count sed extraction blocks in test/e2e-gateway-isolation.sh — 9 tests each extract 2-6 functions.
  • Missing regression test: Verify refactored helper passes all existing e2e tests.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Tests 30-30h each have { sed -n "/^resolve_mutable_config_normalizer()... pattern to extract functions.

PRA-17 Improvement — resolve_mutable_config_normalizer() has redundant repo-relative and script-dir-relative paths

  • Location: scripts/nemoclaw-start.sh:510
  • Category: correctness
  • Problem: resolve_mutable_config_normalizer() has redundant repo-relative and script-dir-relative fallback paths. Both resolve to the same source-tree normalizer for non-root dev/test use.
  • Impact: Redundant code paths increase complexity and potential for divergence; both serve identical dev-only purpose.
  • Suggested action: Remove one of the two checkout-relative fallback paths to reduce attack surface and complexity.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 530-540: both repo-relative and script-dir-relative paths exist for non-root fallback.
  • Missing regression test: Verify non-root test harnesses still work with single fallback.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Line 533: if [ -f "scripts/lib/normalize_mutable_config_perms.py" ]; Line 538: normalizer="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/normalize_mutable_config_perms.py"

PRA-18 Improvement — Documentation for root config recovery boundary missing test 30g reference

  • Location: docs/reference/troubleshooting.mdx:1300
  • Category: correctness
  • Problem: Documentation for root config recovery boundary mentions the behavior but does not reference test 30g (ROOT_BOOT_RECLAIM_OK) explicitly so operators can find the authoritative validation.
  • Impact: Operators troubleshooting root-owned config issues cannot easily find the authoritative test that validates the recovery behavior.
  • Suggested action: Add a reference to test 30g (ROOT_BOOT_RECLAIM_OK) in the troubleshooting documentation.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: grep '30g\|ROOT_BOOT' docs/reference/troubleshooting.mdx — no matches found.
  • Missing regression test: Documentation update with test reference.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: troubleshooting.mdx lines 1300-1310 describe root-owned 700/600 recovery but no test reference.

PRA-19 Improvement — Test 30g monkey-patches python3 shell function - fragile TOCTOU test

  • Location: test/e2e-gateway-isolation.sh:1100
  • Category: correctness
  • Problem: Test 30g monkey-patches the python3 shell function to simulate a TOCTOU race during classify. This is clever but fragile — it tests the classify phase race but not the reclaim phase, and depends on shell function override semantics.
  • Impact: Test depends on shell function override behavior which could change; only tests classify race, not reclaim race; not a robust concurrent test.
  • Suggested action: Consider a more robust race test using actual concurrent processes, or document why the shell function override is the appropriate test strategy for this TOCTOU.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Test 30g lines 1100-1120: python3() function override that mv's the config dir during classify.
  • Missing regression test: More robust concurrent TOCTOU test for reclaim phase.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Test 30g defines python3() { if [ "${2:-}" = "-" ] && [ ! -e /tmp/reclaim-open-raced ]; then ... mv /sandbox/.openclaw /sandbox/.openclaw-raced; }
Simplification opportunities: 3 possible cuts, net -223 lines possible

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-15 shrink (scripts/nemoclaw-start.sh:510): repo-relative and script-dir-relative fallback paths in resolve_mutable_config_normalizer()
    • Replacement: Single dev fallback via NEMOCLAW_MUTABLE_CONFIG_NORMALIZER env var
    • Net: -15 lines
    • Safety boundary: Root path must still fail closed if installed helper missing; dev fallback only for non-root
  • PRA-16 delete (test/e2e-gateway-isolation.sh:539): Repeated sed function extraction blocks in each e2e test
    • Replacement: Single sourced helper script with function definitions
    • Net: -200 lines
    • Safety boundary: Helper must be sourced from the built image, not from source tree
  • PRA-17 delete (scripts/nemoclaw-start.sh:510): One of the two checkout-relative fallback paths (repo-relative or script-dir-relative)
    • Replacement: Single checkout-relative fallback
    • Net: -8 lines
    • Safety boundary: Dev fallback only used when running as non-root; root path uses only installed helper
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — ConcurrentReclaimRace: spawn N parallel reclaim_if_unsealed on same config dir; verify exactly 1 succeeds, tree ends 2770/660 sandbox:sandbox. Runtime/sandbox/infrastructure paths need behavioral runtime validation: descriptor-pinned operations, privilege boundary (SCM_RIGHTS), crash recovery, concurrent access, and non-Linux compatibility. Unit tests cover logic but not runtime behavior under real privilege separation.
  • PRA-T2 Runtime validation — ConcurrentCaptureVsReclaim: loop write_openclaw_config_baseline while reclaim runs; verify baseline root:440, tree 2770/660, no corruption. Runtime/sandbox/infrastructure paths need behavioral runtime validation: descriptor-pinned operations, privilege boundary (SCM_RIGHTS), crash recovery, concurrent access, and non-Linux compatibility. Unit tests cover logic but not runtime behavior under real privilege separation.
  • PRA-T3 Runtime validation — ReclaimCrashRecovery: SIGKILL child after fixed-files reclaimed but before dir handoff; verify startup idempotently recovers or fails closed. Runtime/sandbox/infrastructure paths need behavioral runtime validation: descriptor-pinned operations, privilege boundary (SCM_RIGHTS), crash recovery, concurrent access, and non-Linux compatibility. Unit tests cover logic but not runtime behavior under real privilege separation.
  • PRA-T4 Runtime validation — BaselineLockCrashRecovery: SIGKILL after os.replace before fsync; verify baseline state consistent. Runtime/sandbox/infrastructure paths need behavioral runtime validation: descriptor-pinned operations, privilege boundary (SCM_RIGHTS), crash recovery, concurrent access, and non-Linux compatibility. Unit tests cover logic but not runtime behavior under real privilege separation.
  • PRA-T5 Runtime validation — SCMRightsUnitTest: unit test receive_owner_fd/child_socket protocol with credential validation. Runtime/sandbox/infrastructure paths need behavioral runtime validation: descriptor-pinned operations, privilege boundary (SCM_RIGHTS), crash recovery, concurrent access, and non-Linux compatibility. Unit tests cover logic but not runtime behavior under real privilege separation.
  • PRA-T6 Concurrent reclaim race test missing — Add an e2e test spawning N parallel reclaim_if_unsealed processes on the same config dir; verify exactly one succeeds and the tree ends in 2770/660 sandbox:sandbox ownership.
  • PRA-T7 Concurrent capture vs reclaim test missing — Add an e2e test running write_openclaw_config_baseline in a loop while reclaim executes; verify no corruption, baseline ends root:440, config tree ends 2770/660.
  • PRA-T8 Socketpair SCM_RIGHTS credential passing untested at unit level — Add a focused unit test for the receive_owner_fd / child socket protocol, or document that e2e tests 30-30h are the authoritative coverage for the privilege boundary.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Required — Concurrent reclaim race test missing

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: tests
  • Problem: The Python normalizer uses descriptor pinning to serialize reclaim_if_unsealed but no test validates concurrent callers on the same config directory. Two processes calling reclaim_if_unsealed simultaneously could race, potentially corrupting the config tree or leaving it in an inconsistent state, breaking the sandbox:sandbox 2770/660 contract.
  • Impact: If a race condition exists, one reclaim could corrupt the config tree or leave it in an inconsistent state, breaking the sandbox:sandbox 2770/660 contract and causing sandbox/gateway write failures.
  • Required action: Add an e2e test spawning N parallel reclaim_if_unsealed processes on the same config dir; verify exactly one succeeds and the tree ends in 2770/660 sandbox:sandbox ownership.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/e2e-gateway-isolation.sh for 'concurrent reclaim' — no such test exists.
  • Missing regression test: Concurrent reclaim race test covering parallel mutation attempts on the same config directory with pinned descriptors.
  • Done when: The required change is committed and verification passes: Search test/e2e-gateway-isolation.sh for 'concurrent reclaim' — no such test exists.
  • Evidence: reclaim_if_unsealed opens config dir once with O_NOFOLLOW and classifies+reclaims on same pinned descriptor (normalize_mutable_config_perms.py:260-340), but no test validates concurrent callers.

PRA-2 Required — Concurrent capture vs reclaim test missing

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: tests
  • Problem: Both write_openclaw_config_baseline (capture) and reclaim_collapsed_mutable_config operate on the config tree with pinned descriptors. Their interaction under concurrency is unverified — if capture runs during reclaim, the baseline could be written from a partially-reclaimed tree or the reclaim could see a partially-captured baseline.
  • Impact: Baseline corruption or inconsistent permissions; config tree could end in wrong ownership/mode state, breaking gateway writes or shields recovery.
  • Required action: Add an e2e test running write_openclaw_config_baseline in a loop while reclaim executes; verify no corruption, baseline ends root:440, config tree ends 2770/660.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Search test/e2e-gateway-isolation.sh for 'concurrent capture' or 'capture.*reclaim' — no such test exists.
  • Missing regression test: Concurrent capture vs reclaim test covering overlapping pinned-descriptor operations.
  • Done when: The required change is committed and verification passes: Search test/e2e-gateway-isolation.sh for 'concurrent capture' or 'capture.*reclaim' — no such test exists.
  • Evidence: Both capture_recovery_baseline (line 1050+) and reclaim_if_unsealed (line 260+) use pinned descriptors but no test covers their concurrent execution.

PRA-3 Required — Root-owned config reclaim is a workaround for unidentified regression #6300

PRA-4 Resolve/justify — Startup ordering workaround for orphan-freeze discriminator overlap

PRA-5 Resolve/justify — Source-of-truth review needed for root-owned config reclaim (#6300 workaround)

PRA-6 Resolve/justify — fd_mount_id() Linux-only restriction undocumented

  • Location: scripts/lib/normalize_mutable_config_perms.py:150
  • Category: docs
  • Problem: fd_mount_id() fails closed on non-Linux (raises UnsafeTree) with no documentation that this is intentional for production containers. The classify_config_path() calls fd_mount_id() which will break non-root fallback paths on macOS/non-Linux test environments.
  • Impact: Non-Linux test environments (macOS CI) may fail seal classification unexpectedly, causing false negatives in config normalization.
  • Recommended action: Add comment to fd_mount_id() explaining Linux-only restriction is intentional for production containers; test environments should use the non-root fallback path which doesn't require mount ID verification.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read fd_mount_id() at line 150 — raises UnsafeTree on non-Linux with no explanatory comment.
  • Missing regression test: Verify non-root fallback path in normalize_mutable_config_perms() is exercised on non-Linux platforms.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read fd_mount_id() at line 150 — raises UnsafeTree on non-Linux with no explanatory comment.
  • Evidence: fd_mount_id() line 150-170: if not sys.platform.startswith('linux'): raise UnsafeTree()

PRA-7 Resolve/justify — Non-root env helper fallback rationale missing per-branch comments

  • Location: scripts/nemoclaw-start.sh:515
  • Category: docs
  • Problem: Non-root env helper fallback rationale should explicitly note dev-only scope at each fallback branch. Current comment says 'exist solely for non-root developer/test harnesses' but could be more explicit at each branch.
  • Impact: Developers may not realize the env/checkout fallbacks are dev-only and cannot change ownership in production, leading to confusion about security boundary.
  • Recommended action: Add inline comments at each fallback branch (NEMOCLAW_MUTABLE_CONFIG_NORMALIZER, repo-relative, script-dir-relative) explicitly stating 'dev/test only — cannot change ownership in production'.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Lines 515-540: comment at top of function but not at each branch.
  • Missing regression test: N/A — documentation improvement.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Lines 515-540: comment at top of function but not at each branch.
  • Evidence: resolve_mutable_config_normalizer() has 4 fallback paths with only top-level dev-only comment.

PRA-8 Resolve/justify — Socketpair SCM_RIGHTS credential passing untested at unit level

  • Location: test/nemoclaw-start-perms.test.ts:450
  • Category: tests
  • Problem: Socketpair SCM_RIGHTS credential passing (receive_owner_fd / child socket protocol) untested at unit level. The protocol transfers pinned directory descriptors and validates credentials via SCM_CREDENTIALS, but no focused unit test exercises this privilege boundary.
  • Impact: Privilege boundary validation relies solely on e2e tests; a regression in credential validation could go undetected until integration testing.
  • Recommended action: Add a focused unit test for the receive_owner_fd / child socket protocol, or document that e2e tests 30-30h are the authoritative coverage for the privilege boundary.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Search test/nemoclaw-start-perms.test.ts for 'receive_owner_fd' or 'SCM_RIGHTS' — no unit test found.
  • Missing regression test: Unit test for socketpair SCM_RIGHTS credential passing and descriptor transfer.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Search test/nemoclaw-start-perms.test.ts for 'receive_owner_fd' or 'SCM_RIGHTS' — no unit test found.
  • Evidence: receive_owner_fd() validates credentials == [(child_pid, expected_uid, expected_gid)] but no unit test exercises this.

PRA-9 Resolve/justify — Reclaim intermediate states not crash-tested

  • Location: scripts/lib/normalize_mutable_config_perms.py:380
  • Category: correctness
  • Problem: reclaim_if_unsealed() has multiple steps (open fixed files, reclaim fixed files, fchmod/fchown/fchmod directory handoff). A crash (SIGKILL) between steps could leave the tree in an inconsistent state.
  • Impact: If the process is killed between reclaiming fixed files and handing off the directory, the config tree could be left with sandbox-owned files but root-owned directory, breaking the 2770/660 contract.
  • Recommended action: Add a test simulating crash between reclaim steps (e.g., SIGKILL after fixed files reclaimed but before directory handoff) and verify startup idempotently recovers or fails closed.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Test 30g tests a race at classify time (monkey-patched python3) but not intermediate reclaim steps.
  • Missing regression test: Crash recovery test for reclaim intermediate states.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Test 30g tests a race at classify time (monkey-patched python3) but not intermediate reclaim steps.
  • Evidence: reclaim_if_unsealed() lines 380-420: multiple fchown/fchmod/fchmod steps with no crash recovery verification.

PRA-10 Resolve/justify — fd_mount_id() arbitrary 4096 byte read limit

  • Location: scripts/lib/normalize_mutable_config_perms.py:162
  • Category: correctness
  • Problem: fd_mount_id() reads exactly 4097 bytes and fails if payload > 4096. This arbitrary limit could truncate on systems with many mounts (/proc/self/fdinfo/N can exceed 4KB).
  • Impact: On systems with many mount points, the fdinfo read could exceed 4096 bytes, causing false UnsafeTree failures.
  • Recommended action: Either increase buffer with documented rationale, or read in a loop until EOF. Document the limit if kept.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 162: payload = os.read(fdinfo_fd, 4097); if len(payload) > 4096: raise UnsafeTree()
  • Missing regression test: Test fd_mount_id() with synthetic /proc/self/fdinfo content exceeding 4096 bytes.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 162: payload = os.read(fdinfo_fd, 4097); if len(payload) > 4096: raise UnsafeTree().
  • Evidence: fd_mount_id() uses fixed 4097 byte read with hard fail on overflow.

PRA-11 Resolve/justify — fd_mount_id() fails closed on non-Linux with no graceful degradation for test environments

  • Location: scripts/lib/normalize_mutable_config_perms.py:150
  • Category: security
  • Problem: fd_mount_id() fails closed on non-Linux with no graceful degradation for test environments. classify_config_path() calls fd_mount_id() which raises UnsafeTree on non-Linux, making seal classification fail on macOS CI runners unless the non-root fallback path avoids this code.
  • Impact: Non-Linux CI runners (macOS) cannot run seal classification, potentially missing regressions in the classify path.
  • Recommended action: Ensure the non-root fallback path in normalize_mutable_config_perms() is exercised on non-Linux platforms, or document that mount ID verification is Linux-only and non-root tests must not require it.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: classify_config_path() line 230 calls fd_mount_id() for both root and parent fd — will fail on non-Linux.
  • Missing regression test: CI test on non-Linux platform exercising non-root config normalization.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: classify_config_path() line 230 calls fd_mount_id() for both root and parent fd — will fail on non-Linux.
  • Evidence: fd_mount_id() raises UnsafeTree on non-Linux; classify_config_path() calls it for both root_fd and parent_fd.

PRA-12 Resolve/justify — Overlapping open PRs modify nemoclaw-start.sh and nemoclaw-start.test.ts

PRA-13 Resolve/justify — classify_config_path() breaks non-Linux non-root paths via fd_mount_id()

  • Location: scripts/lib/normalize_mutable_config_perms.py:230
  • Category: security
  • Problem: classify_config_path() calls fd_mount_id() for both root_fd and parent_fd on non-root paths. On non-Linux, this will raise UnsafeTree and return INDETERMINATE, potentially breaking non-root config normalization on macOS CI.
  • Impact: classify_openclaw_config_seal() (used by startup) calls classify_config_path() which calls fd_mount_id(). On macOS, this fails closed even for valid mutable configs.
  • Recommended action: Verify non-root path in normalize_mutable_config_perms() avoids classify_config_path() on non-Linux, or add a platform guard. The non-root path calls normalize_owner_tree() which doesn't use classify_config_path(), but classify_openclaw_config_seal() (used by startup) does.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: classify_openclaw_config_seal() calls classify_config_path() which calls fd_mount_id(). Test on non-Linux runner.
  • Missing regression test: Non-Linux CI test for classify_openclaw_config_seal().
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: classify_openclaw_config_seal() calls classify_config_path() which calls fd_mount_id(). Test on non-Linux runner.
  • Evidence: classify_config_path() line 230: fd_mount_id(root_fd) != fd_mount_id(parent_fd) — called by classify_openclaw_config_seal() used in prepare_openclaw_config_startup().

PRA-14 Resolve/justify — lock_recovery_baseline() crash between os.replace and fsync

  • Location: scripts/lib/normalize_mutable_config_perms.py:450
  • Category: correctness
  • Problem: lock_recovery_baseline() has complex temp-file staging (temp_name, temp_fd, temp_identity) with cleanup in finally block. A crash between os.replace() and os.fsync() could leave temp files or an inconsistent baseline state.
  • Impact: If process dies after os.replace but before fsync, the baseline file may not be durably written, causing recovery to fail or use stale baseline on next startup.
  • Recommended action: Add crash test for lock_recovery_baseline intermediate states (SIGKILL after os.replace but before fsync).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: lock_recovery_baseline() lines 450-550: multiple os.replace/fsync steps with temp file cleanup in finally.
  • Missing regression test: Crash recovery test for baseline locking intermediate states.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: lock_recovery_baseline() lines 450-550: multiple os.replace/fsync steps with temp file cleanup in finally.
  • Evidence: lock_recovery_baseline() uses os.replace then os.fsync with temp file cleanup in finally block.

PRA-15 Improvement — resolve_mutable_config_normalizer() can be simplified to 2 paths

  • Location: scripts/nemoclaw-start.sh:510
  • Category: correctness
  • Problem: resolve_mutable_config_normalizer() has 4 resolution paths (installed, NEMOCLAW_MUTABLE_CONFIG_NORMALIZER env, repo-relative, script-dir-relative). Can simplify to 2 paths (installed + env for dev) since repo-relative and script-dir-relative are redundant for non-root test harnesses.
  • Impact: Unnecessary complexity and attack surface in normalizer resolution; two checkout-relative paths serve the same dev-only purpose.
  • Suggested action: Simplify to 2 paths (installed + NEMOCLAW_MUTABLE_CONFIG_NORMALIZER for dev) and verify all test scenarios still pass.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 510-540 show 4 fallback paths; repo-relative and script-dir-relative serve same purpose.
  • Missing regression test: Verify all existing test scenarios pass with simplified resolver.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: resolve_mutable_config_normalizer() has installed path, env var, repo-relative, and script-dir-relative fallbacks.

PRA-16 Improvement — Extract shared e2e test helper to avoid sed duplication

  • Location: test/e2e-gateway-isolation.sh:539
  • Category: architecture
  • Problem: Each e2e test (30-30h) independently extracts shell functions via sed from /usr/local/bin/nemoclaw-start. This duplicates ~50 lines of extraction boilerplate per test.
  • Impact: Maintenance burden; changes to function signatures require updating 9+ test blocks; risk of extraction drift.
  • Suggested action: Extract common test setup into a sourced helper script; verify all 9 test variants still pass.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Count sed extraction blocks in test/e2e-gateway-isolation.sh — 9 tests each extract 2-6 functions.
  • Missing regression test: Verify refactored helper passes all existing e2e tests.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Tests 30-30h each have { sed -n "/^resolve_mutable_config_normalizer()... pattern to extract functions.

PRA-17 Improvement — resolve_mutable_config_normalizer() has redundant repo-relative and script-dir-relative paths

  • Location: scripts/nemoclaw-start.sh:510
  • Category: correctness
  • Problem: resolve_mutable_config_normalizer() has redundant repo-relative and script-dir-relative fallback paths. Both resolve to the same source-tree normalizer for non-root dev/test use.
  • Impact: Redundant code paths increase complexity and potential for divergence; both serve identical dev-only purpose.
  • Suggested action: Remove one of the two checkout-relative fallback paths to reduce attack surface and complexity.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Lines 530-540: both repo-relative and script-dir-relative paths exist for non-root fallback.
  • Missing regression test: Verify non-root test harnesses still work with single fallback.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Line 533: if [ -f "scripts/lib/normalize_mutable_config_perms.py" ]; Line 538: normalizer="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/normalize_mutable_config_perms.py"

PRA-18 Improvement — Documentation for root config recovery boundary missing test 30g reference

  • Location: docs/reference/troubleshooting.mdx:1300
  • Category: correctness
  • Problem: Documentation for root config recovery boundary mentions the behavior but does not reference test 30g (ROOT_BOOT_RECLAIM_OK) explicitly so operators can find the authoritative validation.
  • Impact: Operators troubleshooting root-owned config issues cannot easily find the authoritative test that validates the recovery behavior.
  • Suggested action: Add a reference to test 30g (ROOT_BOOT_RECLAIM_OK) in the troubleshooting documentation.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: grep '30g\|ROOT_BOOT' docs/reference/troubleshooting.mdx — no matches found.
  • Missing regression test: Documentation update with test reference.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: troubleshooting.mdx lines 1300-1310 describe root-owned 700/600 recovery but no test reference.

PRA-19 Improvement — Test 30g monkey-patches python3 shell function - fragile TOCTOU test

  • Location: test/e2e-gateway-isolation.sh:1100
  • Category: correctness
  • Problem: Test 30g monkey-patches the python3 shell function to simulate a TOCTOU race during classify. This is clever but fragile — it tests the classify phase race but not the reclaim phase, and depends on shell function override semantics.
  • Impact: Test depends on shell function override behavior which could change; only tests classify race, not reclaim race; not a robust concurrent test.
  • Suggested action: Consider a more robust race test using actual concurrent processes, or document why the shell function override is the appropriate test strategy for this TOCTOU.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Test 30g lines 1100-1120: python3() function override that mv's the config dir during classify.
  • Missing regression test: More robust concurrent TOCTOU test for reclaim phase.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: Test 30g defines python3() { if [ "${2:-}" = "-" ] && [ ! -e /tmp/reclaim-open-raced ]; then ... mv /sandbox/.openclaw /sandbox/.openclaw-raced; }

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: test-e2e-gateway-isolation, shields-config
Optional E2E: test-e2e-sandbox, runtime-overrides

Dispatch hint: shields-config

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • test-e2e-gateway-isolation (medium): Required because the PR changes the production sandbox entrypoint and descriptor-safe config permission normalizer, including privileged root-owned tree reclaim and fail-closed security behavior. This job builds the production image and runs test/e2e-gateway-isolation.sh against it.
  • shields-config (high): Required because the startup changes affect OpenClaw config seal/recover ordering and the mutable vs shields-locked posture boundary. The live shields-config job exercises real install/onboard plus sandbox shields/config behavior rather than only unit or image-local checks.

Optional E2E

  • test-e2e-sandbox (medium): Useful adjacent confidence for the built sandbox test image because the changed entrypoint and config tree normalization can affect general sandbox startup behavior, but gateway-isolation and shields-config are the focused merge-blocking checks.
  • runtime-overrides (high): Optional because runtime overrides also flow through the production image startup path and mutable OpenClaw config handling, but the PR does not primarily change model/provider override logic.

New E2E recommendations

  • live sandbox persisted config reclaim (high): Existing image-local gateway isolation coverage is strong, but a dedicated live E2E would better prove that an actual OpenShell-backed sandbox with a persisted root-owned 0700/0600 OpenClaw config tree is reclaimed only for the exact mutable-drift signature across a real sandbox restart, while sealed and ambiguous states remain fail-closed.
    • Suggested test: Add a live OpenClaw sandbox restart regression that creates the exact persisted root:root 0700/0600 openclaw.json/.config-hash posture, restarts the sandbox, verifies restoration to sandbox:sandbox 2770/660 and gateway write access, then repeats with sealed, symlink, hardlink, mount, and ambiguous-mode variants that must not be repaired.

Dispatch hint

  • Workflow: .github/workflows/e2e.yaml
  • jobs input: shields-config

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: shields-config
Optional E2E targets: ubuntu-repo-cloud-openclaw

Dispatch required E2E targets:

  • gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=shields-config

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • shields-config: OpenClaw startup and mutable config permission normalization changed, including root-owned 700/600 reclaim/fail-closed behavior and interaction with startup config recovery. The wired shields-config live job is the focused OpenClaw config/shields boundary regression: it performs a fresh live install, checks mutable .openclaw posture, exercises the documented openclaw doctor --fix exec cleanup path, validates gateway writes, and runs real shields up/down config transitions.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field jobs=shields-config

Optional E2E targets

  • ubuntu-repo-cloud-openclaw: Optional broad registry smoke for the standard Ubuntu OpenClaw onboarding path, since nemoclaw-start.sh changes affect normal sandbox startup beyond the focused shields/config job.
    • Dispatch: gh workflow run e2e.yaml --ref <pr-head-ref> --field targets=ubuntu-repo-cloud-openclaw

Relevant changed files

  • scripts/lib/normalize_mutable_config_perms.py
  • scripts/nemoclaw-start.sh
  • test/e2e-gateway-isolation.sh

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Changes requested

Merge posture: Do not merge yet
Primary next action: Resolve or justify PRA-1: Source-of-truth review needed: Boot-time recovery of root-owned mutable `/sandbox/.openclaw`.
Open items: 0 required · 2 warnings · 0 suggestions · 7 test follow-ups
Since last review: 1 prior item resolved · 1 still applies · 0 new items found

Action checklist

  • PRA-1 Resolve or justify: Source-of-truth review needed: Boot-time recovery of root-owned mutable `/sandbox/.openclaw`
  • PRA-2 Resolve or justify: Verify the [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 fresh onboard/startup path, not only seeded reclaim in scripts/nemoclaw-start.sh:587
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Acceptance clause
  • PRA-T3 Add or justify test follow-up: Acceptance clause
  • PRA-T4 Add or justify test follow-up: Acceptance clause
  • PRA-T5 Add or justify test follow-up: Acceptance clause
  • PRA-T6 Add or justify test follow-up: Acceptance clause
  • PRA-T7 Add or justify test follow-up: Boot-time recovery of root-owned mutable `/sandbox/.openclaw`

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify acceptance scripts/nemoclaw-start.sh:587 Add or identify trusted in-repo runtime evidence for the fresh default OpenClaw sandbox path. The evidence should show a fresh install/onboard/startup reaches `/sandbox/.openclaw` as `sandbox:sandbox 2770`, fixed config files as `sandbox:sandbox 660`, and that the sandbox user can write through `nemoclaw <sandbox> exec`.
Review findings by urgency: 0 required fixes, 2 items to resolve/justify, 0 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: Boot-time recovery of root-owned mutable `/sandbox/.openclaw`

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Seeded unit/integration and production-image tests cover exact reclaim, startup ordering, baseline preservation, sealed no-op, ambiguous states, parent mismatch, hardlinks, symlinks, path disappearance, mounted trees, non-root refusal, and missing identity capabilities. The fresh default onboard/startup path remains the follow-up captured in the finding.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `scripts/nemoclaw-start.sh` classifies and reclaims seal state 1 before transaction recovery; `scripts/lib/normalize_mutable_config_perms.py` revalidates under pinned descriptors; docs add the helper to the TCB boundary. Missing fresh-path evidence is covered by the acceptance finding.

PRA-2 Resolve/justify — Verify the #6300 fresh onboard/startup path, not only seeded reclaim

  • Location: scripts/nemoclaw-start.sh:587
  • Category: acceptance
  • Problem: The PR now implements a tight descriptor-pinned reclaim path for the exact root-owned mutable-config signature and adds strong seeded negative coverage. The linked issue's reproducer, however, is a fresh v0.0.74 install/onboard/default-startup flow followed by `nemoclaw <sandbox> exec -- stat` and a sandbox-user write check. The changed tests still seed the invalid ownership manually or use extracted shell-function harnesses rather than exercising that producer path.
  • Impact: A compatibility repair can pass the seeded reclaim tests while a fresh onboarding, image, or runtime producer continues to create or preserve root-owned mutable config trees. That leaves the reported user path and the source-of-truth boundary only partially proven for default shields-down sandboxes.
  • Recommended action: Add or identify trusted in-repo runtime evidence for the fresh default OpenClaw sandbox path. The evidence should show a fresh install/onboard/startup reaches `/sandbox/.openclaw` as `sandbox:sandbox 2770`, fixed config files as `sandbox:sandbox 660`, and that the sandbox user can write through `nemoclaw <sandbox> exec`.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Compare issue [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 steps 1-6 with the changed tests: `test/e2e-gateway-isolation.sh` test 30g manually runs `chown root:root` and `chmod 700/600`; `test/nemoclaw-start-perms.test.ts` uses synthetic shell-function harnesses and root-only fixtures. Existing `test/e2e/live/shields-config.test.ts` has related fresh install/default stat coverage, but it is not changed here and does not exactly exercise the issue's `nemoclaw onboard` plus sandbox-user `touch /sandbox/.openclaw/testfile` path.
  • Missing regression test: Add `fresh default onboard startup restores mutable OpenClaw config ownership and sandbox writes`: create a fresh default OpenClaw sandbox through the same onboard/startup path as [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300, assert `stat -c '%a %U:%G' /sandbox/.openclaw` is `2770 sandbox:sandbox`, assert `openclaw.json` and `.config-hash` are `660 sandbox:sandbox`, then run `nemoclaw <sandbox> exec -- sh -c 'touch /sandbox/.openclaw/testfile && printf " " >> /sandbox/.openclaw/openclaw.json'` and assert success.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Compare issue [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 steps 1-6 with the changed tests: `test/e2e-gateway-isolation.sh` test 30g manually runs `chown root:root` and `chmod 700/600`; `test/nemoclaw-start-perms.test.ts` uses synthetic shell-function harnesses and root-only fixtures. Existing `test/e2e/live/shields-config.test.ts` has related fresh install/default stat coverage, but it is not changed here and does not exactly exercise the issue's `nemoclaw onboard` plus sandbox-user `touch /sandbox/.openclaw/testfile` path.
  • Evidence: `scripts/nemoclaw-start.sh` documents that no in-repo producer has been identified and accepts only the narrow root-owned fixture; `test/e2e-gateway-isolation.sh` test 30g seeds root ownership and modes before calling startup helpers; `test/nemoclaw-start-perms.test.ts` covers classification, ordering, and reclaim with synthetic fixtures.

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

  • None.
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — Add `fresh default onboard startup restores mutable OpenClaw config ownership and sandbox writes`: create a fresh default OpenClaw sandbox through the same onboard/startup path as [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300, assert `/sandbox/.openclaw` is `sandbox:sandbox 2770`, assert `openclaw.json` and `.config-hash` are `sandbox:sandbox 660`, and assert `nemoclaw <sandbox> exec -- sh -c 'touch /sandbox/.openclaw/testfile && printf " " >> /sandbox/.openclaw/openclaw.json'` succeeds.. The changed behavior runs in privileged sandbox startup and filesystem recovery paths. Unit and seeded production-image negative coverage are strong, but the linked acceptance path is a fresh onboarding/startup behavior that is not statically or synthetically proven by the changed tests.
  • PRA-T2 Acceptance clause — Fixes [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 — add test evidence or identify existing coverage. The PR repairs the exact root-owned mutable config signature and verifies seeded reclaim/fail-closed behavior, but the fresh install/onboard/default startup reproducer from [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 is not exercised by changed tests.
  • PRA-T3 Acceptance clause — In v0.0.74, the `/sandbox/.openclaw/` directory ownership changed from `sandbox:sandbox` to `root:root`. — add test evidence or identify existing coverage. `normalize_mutable_config_perms` now detects root-owned config directories and `reclaim_if_unsealed` accepts only `root:root 0700` with fixed `root:root 0600` files. Tests seed that state and verify reclaim, but no changed test proves the real v0.0.74 fresh-onboard producer path.
  • PRA-T4 Acceptance clause — The sandbox user can no longer write to config files (e.g. `openclaw.json`) in the default mutable (shields-down) state. — add test evidence or identify existing coverage. Reclaim changes the directory to `sandbox:sandbox 2770` and fixed files to `sandbox:sandbox 660`. `test/e2e-gateway-isolation.sh` test 30g and `test/nemoclaw-start-perms.test.ts` verify sandbox/nobody writes after seeded reclaim, but the changed tests do not use the exact `nemoclaw <sandbox> exec -- touch /sandbox/.openclaw/testfile` path.
  • PRA-T5 Acceptance clause — This is a regression from v0.0.73 where `.openclaw/` was `sandbox:sandbox` and writable. — add test evidence or identify existing coverage. The PR restores the intended current mutable contract for seeded root-owned drift, and docs describe `2770/660` as the supported mutable state. No changed test compares v0.0.73 to v0.0.74 or exercises the exact upgrade boundary.
  • PRA-T6 Acceptance clause — Possibly a side-effect of the fix for NVBug #6395987 / GitHub Issue [DGX Spark][Sandbox] openclaw doctor --fix collapses /sandbox/.openclaw permissions, breaking gateway group-writable contract #6047 (openclaw doctor --fix permission collapse). — add test evidence or identify existing coverage. The PR keeps the existing [DGX Spark][Sandbox] openclaw doctor --fix collapses /sandbox/.openclaw permissions, breaking gateway group-writable contract #6047 sandbox-owned mode-collapse repair separate from the new root-collapsed path and preserves [DGX Spark][Sandbox] openclaw doctor --fix collapses /sandbox/.openclaw permissions, breaking gateway group-writable contract #6047 tests. The root-owned producer remains unidentified in trusted code.
  • PRA-T7 Boot-time recovery of root-owned mutable `/sandbox/.openclaw` — Seeded unit/integration and production-image tests cover exact reclaim, startup ordering, baseline preservation, sealed no-op, ambiguous states, parent mismatch, hardlinks, symlinks, path disappearance, mounted trees, non-root refusal, and missing identity capabilities. The fresh default onboard/startup path remains the follow-up captured in the finding.. `scripts/nemoclaw-start.sh` classifies and reclaims seal state 1 before transaction recovery; `scripts/lib/normalize_mutable_config_perms.py` revalidates under pinned descriptors; docs add the helper to the TCB boundary. Missing fresh-path evidence is covered by the acceptance finding.
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: Boot-time recovery of root-owned mutable `/sandbox/.openclaw`

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Seeded unit/integration and production-image tests cover exact reclaim, startup ordering, baseline preservation, sealed no-op, ambiguous states, parent mismatch, hardlinks, symlinks, path disappearance, mounted trees, non-root refusal, and missing identity capabilities. The fresh default onboard/startup path remains the follow-up captured in the finding.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: `scripts/nemoclaw-start.sh` classifies and reclaims seal state 1 before transaction recovery; `scripts/lib/normalize_mutable_config_perms.py` revalidates under pinned descriptors; docs add the helper to the TCB boundary. Missing fresh-path evidence is covered by the acceptance finding.

PRA-2 Resolve/justify — Verify the #6300 fresh onboard/startup path, not only seeded reclaim

  • Location: scripts/nemoclaw-start.sh:587
  • Category: acceptance
  • Problem: The PR now implements a tight descriptor-pinned reclaim path for the exact root-owned mutable-config signature and adds strong seeded negative coverage. The linked issue's reproducer, however, is a fresh v0.0.74 install/onboard/default-startup flow followed by `nemoclaw <sandbox> exec -- stat` and a sandbox-user write check. The changed tests still seed the invalid ownership manually or use extracted shell-function harnesses rather than exercising that producer path.
  • Impact: A compatibility repair can pass the seeded reclaim tests while a fresh onboarding, image, or runtime producer continues to create or preserve root-owned mutable config trees. That leaves the reported user path and the source-of-truth boundary only partially proven for default shields-down sandboxes.
  • Recommended action: Add or identify trusted in-repo runtime evidence for the fresh default OpenClaw sandbox path. The evidence should show a fresh install/onboard/startup reaches `/sandbox/.openclaw` as `sandbox:sandbox 2770`, fixed config files as `sandbox:sandbox 660`, and that the sandbox user can write through `nemoclaw <sandbox> exec`.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Compare issue [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 steps 1-6 with the changed tests: `test/e2e-gateway-isolation.sh` test 30g manually runs `chown root:root` and `chmod 700/600`; `test/nemoclaw-start-perms.test.ts` uses synthetic shell-function harnesses and root-only fixtures. Existing `test/e2e/live/shields-config.test.ts` has related fresh install/default stat coverage, but it is not changed here and does not exactly exercise the issue's `nemoclaw onboard` plus sandbox-user `touch /sandbox/.openclaw/testfile` path.
  • Missing regression test: Add `fresh default onboard startup restores mutable OpenClaw config ownership and sandbox writes`: create a fresh default OpenClaw sandbox through the same onboard/startup path as [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300, assert `stat -c '%a %U:%G' /sandbox/.openclaw` is `2770 sandbox:sandbox`, assert `openclaw.json` and `.config-hash` are `660 sandbox:sandbox`, then run `nemoclaw <sandbox> exec -- sh -c 'touch /sandbox/.openclaw/testfile && printf " " >> /sandbox/.openclaw/openclaw.json'` and assert success.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Compare issue [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 steps 1-6 with the changed tests: `test/e2e-gateway-isolation.sh` test 30g manually runs `chown root:root` and `chmod 700/600`; `test/nemoclaw-start-perms.test.ts` uses synthetic shell-function harnesses and root-only fixtures. Existing `test/e2e/live/shields-config.test.ts` has related fresh install/default stat coverage, but it is not changed here and does not exactly exercise the issue's `nemoclaw onboard` plus sandbox-user `touch /sandbox/.openclaw/testfile` path.
  • Evidence: `scripts/nemoclaw-start.sh` documents that no in-repo producer has been identified and accepts only the narrow root-owned fixture; `test/e2e-gateway-isolation.sh` test 30g seeds root ownership and modes before calling startup helpers; `test/nemoclaw-start-perms.test.ts` covers classification, ordering, and reclaim with synthetic fixtures.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@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 (1)
test/nemoclaw-start-perms.test.ts (1)

399-422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a negative-path test proving reclaim refuses a symlinked config path.

The reclaim describe block only exercises the non-root guard. The symlink-refusal branch (reclaim_collapsed_mutable_config returning rc=1 and leaving the tree untouched) is untested, yet it is the core boundary check. Add a root-gated test that symlinks config_dir/openclaw.json and asserts the reclaim refuses without following the link.

As per path instructions, this boundary requires negative-path tests that prove bypasses are rejected.

🤖 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/nemoclaw-start-perms.test.ts` around lines 399 - 422, The current
reclaim tests cover only the non-root ownership guard, but the symlink-refusal
path in reclaim_collapsed_mutable_config is missing. Add a root-gated
negative-path test in the nemoclaw-start mutable config reclaim describe block
that creates a symlinked config path (or symlinked openclaw.json), invokes
reclaim_collapsed_mutable_config, and verifies it returns rc=1 without changing
the tree. Use the existing reclaimFunction helper and the
reclaim_collapsed_mutable_config call site to locate the behavior under test,
and assert the on-disk ownership/contents remain untouched.

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/nemoclaw-start-perms.test.ts`:
- Around line 399-422: The current reclaim tests cover only the non-root
ownership guard, but the symlink-refusal path in
reclaim_collapsed_mutable_config is missing. Add a root-gated negative-path test
in the nemoclaw-start mutable config reclaim describe block that creates a
symlinked config path (or symlinked openclaw.json), invokes
reclaim_collapsed_mutable_config, and verifies it returns rc=1 without changing
the tree. Use the existing reclaimFunction helper and the
reclaim_collapsed_mutable_config call site to locate the behavior under test,
and assert the on-disk ownership/contents remain untouched.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 20c5f477-5ca5-4a2c-8c5c-ac79423be5ee

📥 Commits

Reviewing files that changed from the base of the PR and between f0d2549 and 019a889.

📒 Files selected for processing (2)
  • scripts/nemoclaw-start.sh
  • test/nemoclaw-start-perms.test.ts

Root reclaim previously discovered entries with `find -P` and then mutated
them by path with separate `chown`/`chmod` calls, leaving a window between
discovery and mutation for a race to swap a pathname. Rewrite the reclaim as
a single descriptor-safe pass: open the root directory with O_NOFOLLOW,
re-verify it is still exactly root:root before touching anything, and
recurse using pinned file descriptors (fchown/fchmod on already-open fds),
verifying each entry's inode identity is unchanged after every mutation.
A tree found already sealed at this point is left untouched instead of
reclaimed.

Add a root-mode regression test that drives the real boot path
(normalize_mutable_config_perms) against a root-owned collapsed config and
asserts the sandbox:sandbox 2770/660 contract and a group-writable touch
both succeed, and replace the non-root test's early-return branch with
it.skipIf/it.runIf gates to keep the test bodies linear.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Tinson Lai <tinsonl@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/nemoclaw-start-perms.test.ts (1)

442-448: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Assert the source patches actually applied — silent no-op risk.

String.prototype.replace with a string pattern replaces only the first match and, critically, returns the original string unchanged if the substring is absent. If the production text ever drifts (e.g. the literal local config_dir="/sandbox/.openclaw" or id -u sandbox), these patches silently no-op, and the test would then run normalize_mutable_config_perms against the real /sandbox/.openclaw and invoke the real id -u sandbox instead of the test fixture — a dangerous and hard-to-debug failure mode rather than a clear test error.

Consider guarding each substitution, e.g. assert the patched string differs from the input (or that the marker exists) before proceeding.

Illustrative guard
+      const applyPatch = (src: string, from: string, to: string) => {
+        const out = src.replace(from, to);
+        if (out === src) throw new Error(`Patch target not found: ${from}`);
+        return out;
+      };
       const normalizeFunction = extractShellFunction("normalize_mutable_config_perms").replace(
         'local config_dir="/sandbox/.openclaw"',
         `local config_dir=${JSON.stringify(configDir)}`,
       );
🤖 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/nemoclaw-start-perms.test.ts` around lines 442 - 448, The string
replacements in test/nemoclaw-start-perms.test.ts can silently no-op if the
expected markers drift, so add explicit guards around the
normalize_mutable_config_perms and reclaimFunction patching. After each
String.prototype.replace call, verify the target substring existed or assert the
patched string actually changed before continuing, so the test fails fast
instead of using the unmodified shell functions. Use the existing symbols
extractShellFunction, normalize_mutable_config_perms, and reclaimFunction to
locate the patch points.

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 `@test/nemoclaw-start-perms.test.ts`:
- Line 404: Update the root-level integration test title in the
nemoclaw-start-perms suite so it follows the required behavior-oriented naming
convention with a local issue-ref suffix. Locate the test case whose title is
“leaves the tree untouched when it cannot reclaim ownership without root” and
append the appropriate final “(`#1234`)”-style reference, matching the pattern
used by the sibling root-only test title.

---

Nitpick comments:
In `@test/nemoclaw-start-perms.test.ts`:
- Around line 442-448: The string replacements in
test/nemoclaw-start-perms.test.ts can silently no-op if the expected markers
drift, so add explicit guards around the normalize_mutable_config_perms and
reclaimFunction patching. After each String.prototype.replace call, verify the
target substring existed or assert the patched string actually changed before
continuing, so the test fails fast instead of using the unmodified shell
functions. Use the existing symbols extractShellFunction,
normalize_mutable_config_perms, and reclaimFunction to locate the patch points.
🪄 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: Enterprise

Run ID: f24d4878-42f1-4552-89b0-33b4f1735c8f

📥 Commits

Reviewing files that changed from the base of the PR and between 019a889 and 19ec64c.

📒 Files selected for processing (2)
  • scripts/nemoclaw-start.sh
  • test/nemoclaw-start-perms.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/nemoclaw-start.sh

Comment thread test/nemoclaw-start-perms.test.ts Outdated
@laitingsheng laitingsheng added integration: openclaw OpenClaw integration behavior NV QA Bugs found by the NVIDIA QA Team area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression labels Jul 6, 2026

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/lib/normalize_mutable_config_perms.py`:
- Around line 238-249: The permission normalization in reclaim_dir currently
applies the generic writable mode to the top-level recovery baseline file
openclaw.json.nemoclaw-baseline, which should remain read-only as a trust
anchor. Update reclaim_dir to special-case that filename in the top-level
regular-file handling, either by excluding it from the blanket | 0o060
adjustment or by restoring a stricter mode immediately after the generic chmod.
Use the existing FIXED_FILES/top_level logic and the file-name check near
open_pinned, os.fchmod, and verify_still_linked to locate the change.
🪄 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: Enterprise

Run ID: 48ad638f-7ab4-412c-b36c-404286101178

📥 Commits

Reviewing files that changed from the base of the PR and between 19ec64c and 8aed13f.

📒 Files selected for processing (4)
  • ci/test-file-size-budget.json
  • scripts/lib/normalize_mutable_config_perms.py
  • scripts/nemoclaw-start.sh
  • test/e2e-gateway-isolation.sh
💤 Files with no reviewable changes (1)
  • test/e2e-gateway-isolation.sh
✅ Files skipped from review due to trivial changes (1)
  • ci/test-file-size-budget.json

Comment thread scripts/lib/normalize_mutable_config_perms.py Outdated
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@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 (1)
test/nemoclaw-start-perms.test.ts (1)

555-601: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen assertions to prove reclaim actually ran, not just that the baseline was spared.

The test only asserts result.status === 0 and that baselineFile is untouched. It never checks that configDir/configFile/hashFile were actually reclaimed (e.g., chowned to the patched nobody uid/gid and set to 2770/660). Per NVIDIA's documented permission states, 0700 root-owned without the immutable bit is a distinct "drift" layout, separate from both the 2770 mutable-default and the immutable host-locked state — so it's not obvious which classifier branch this fixture exercises. As written, the assertions would pass identically whether reclaim executed and skipped the baseline file, or the classifier treated the whole tree as ambiguous/sealed and normalize_mutable_config_perms no-op'd before ever reaching the baseline-exclusion logic.

Add positive assertions that the config directory/files were reclaimed (owner/mode changed) alongside the negative assertion that the baseline stayed put, so the test actually demonstrates "reclaim happened, and the baseline was excluded" rather than "nothing observably changed."

💡 Suggested strengthening
       try {
         const result = runBash(script);
         expect(result.status).toBe(0);
+        expect(fs.statSync(configFile).uid).toBe(nobodyUid);
+        expect(mode(configDir)).toBe(0o2770);
         expect(mode(baselineFile)).toBe(0o440);
         expect(fs.statSync(baselineFile).uid).toBe(beforeBaselineUid);
         expect(fs.statSync(baselineFile).gid).toBe(beforeBaselineGid);
       } finally {

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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/nemoclaw-start-perms.test.ts` around lines 555 - 601, The test in
`leaves a root-owned recovery baseline untouched during reclaim` only proves the
baseline was spared, not that reclaim actually ran. Strengthen the assertions
around `normalize_mutable_config_perms`/`reclaimFunction` by checking
`configDir`, `configFile`, and `hashFile` are actually reclaimed after `runBash`
(ownership changed to the patched nobody uid/gid and modes updated to the
expected mutable defaults), while keeping the existing negative checks for
`baselineFile`. This will ensure the fixture exercises the reclaim path and not
a no-op or ambiguous classifier branch.

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/nemoclaw-start-perms.test.ts`:
- Around line 555-601: The test in `leaves a root-owned recovery baseline
untouched during reclaim` only proves the baseline was spared, not that reclaim
actually ran. Strengthen the assertions around
`normalize_mutable_config_perms`/`reclaimFunction` by checking `configDir`,
`configFile`, and `hashFile` are actually reclaimed after `runBash` (ownership
changed to the patched nobody uid/gid and modes updated to the expected mutable
defaults), while keeping the existing negative checks for `baselineFile`. This
will ensure the fixture exercises the reclaim path and not a no-op or ambiguous
classifier branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 19d72cd8-5e71-4035-b52b-1876eff053d5

📥 Commits

Reviewing files that changed from the base of the PR and between a585660 and b5e0007.

📒 Files selected for processing (2)
  • scripts/lib/normalize_mutable_config_perms.py
  • test/nemoclaw-start-perms.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/lib/normalize_mutable_config_perms.py

@laitingsheng laitingsheng added the v0.0.76 Release target label Jul 7, 2026
@wscurran wscurran added v0.0.77 Release target and removed v0.0.76 Release target labels Jul 7, 2026
@apurvvkumaria apurvvkumaria self-assigned this Jul 7, 2026
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Maintainer hardening update for final head ceecd982bbd4babe567cb4a3d693d59d2f95d9a3:

Tinson Lai's original PR branch, six commits, and PR authorship are retained. The follow-up commits harden the privileged recovery path without superseding his work.

Addressed review feedback:

Advisor concurrency dispositions:

  • Concurrent reclaim does not promise “exactly one succeeds.” Two root callers may safely complete the same idempotent descriptor-pinned handoff; the required invariant is the verified final posture with no external alias mutation.
  • Capture and reclaim have mutually exclusive ownership preconditions (sandbox:sandbox versus the exact root-owned signature), and both revalidate pinned descriptors. A raced ownership/path transition fails closed.

Validation:

  • Focused integration: 25 passed, 2 expected root-only host skips.
  • Final production-image gateway/isolation suite: 45 passed, 0 failed, including the root boot order, real sandbox identity, append/create access, sealed/ambiguous states, hardlink/symlink/open races, parent posture, mount boundary, and missing CAP_SETUID/CAP_SETGID.
  • npx prek run --from-ref origin/main --to-ref HEAD: passed.
  • npm run docs: 0 errors (2 pre-existing hidden Fern warnings).
  • Independent nine-category security re-review: no blocking findings.
  • All four maintainer commits include Signed-off-by trailers and are GitHub Verified (reason: valid).

Remaining merge gate: final-head required CI/automated review and the live shields-config fresh-onboard retest.

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All selected jobs passed

Run: 28888542873
Workflow ref: fix/openclaw-config-perms-self-heal
Requested targets: shields-config
Requested jobs: (default — all default-enabled free-standing jobs; explicit-only jobs openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, sandbox-rlimits-connect, and jetson-nvmap-gpu are skipped unless selected)
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
shields-config ✅ success

@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

🧹 Nitpick comments (1)
test/nemoclaw-start-perms.test.ts (1)

478-492: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test title claims "requires both fixed files" but never exercises the missing-file path.

This test only creates both fixed files in the sealed posture and asserts the positive result, so it never proves that dropping one file (or mismatching one posture) fails to seal. On non-root runners the assertion also collapses to rc=2, identical to the "non-root mutable directory" case, so the sealed-classification logic isn't exercised at all there. Consider a companion negative case (e.g., only openclaw.json sealed, .config-hash missing/writable) that proves the "both required" contract.

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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/nemoclaw-start-perms.test.ts` around lines 478 - 492, The test currently
only covers the positive sealed-state path and can pass without proving that
both fixed files are required. Update the nemoclaw-start-perms test around
runClassify to add a companion negative case that leaves only one of the two
files sealed or omits one entirely, and assert it does not classify as sealed.
Make sure the assertions in this block distinguish the exact root-owned sealed
posture from the mutable/non-root fallback so the test title matches the
behavior being exercised.

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 `@test/nemoclaw-start-perms.test.ts`:
- Line 466: The new seal-classification test titles in this suite are missing
the required local issue-reference suffix. Update the two affected `it`
descriptions in the test block to match the existing sibling titles by appending
the final `(`#1234`)`-style suffix, keeping the behavior-oriented wording intact.

---

Nitpick comments:
In `@test/nemoclaw-start-perms.test.ts`:
- Around line 478-492: The test currently only covers the positive sealed-state
path and can pass without proving that both fixed files are required. Update the
nemoclaw-start-perms test around runClassify to add a companion negative case
that leaves only one of the two files sealed or omits one entirely, and assert
it does not classify as sealed. Make sure the assertions in this block
distinguish the exact root-owned sealed posture from the mutable/non-root
fallback so the test title matches the behavior being exercised.
🪄 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: Enterprise

Run ID: d61e4f84-9a18-4b04-bb5c-58846eb983ae

📥 Commits

Reviewing files that changed from the base of the PR and between a585660 and ceecd98.

📒 Files selected for processing (7)
  • docs/reference/troubleshooting.mdx
  • docs/security/tcb-boundary.mdx
  • scripts/lib/normalize_mutable_config_perms.py
  • scripts/nemoclaw-start.sh
  • test/e2e-gateway-isolation.sh
  • test/nemoclaw-start-perms.test.ts
  • test/repro-2681-group-writable.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/reference/troubleshooting.mdx
  • docs/security/tcb-boundary.mdx

Comment thread test/nemoclaw-start-perms.test.ts Outdated
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All selected jobs passed

Run: 28889048789
Workflow ref: fix/openclaw-config-perms-self-heal
Requested targets: shields-config
Requested jobs: (default — all default-enabled free-standing jobs; explicit-only jobs openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, sandbox-rlimits-connect, and jetson-nvmap-gpu are skipped unless selected)
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
shields-config ✅ success

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All selected jobs passed

Run: 28889481264
Workflow ref: fix/openclaw-config-perms-self-heal
Requested targets: shields-config
Requested jobs: (default — all default-enabled free-standing jobs; explicit-only jobs openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, sandbox-rlimits-connect, and jetson-nvmap-gpu are skipped unless selected)
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
shields-config ✅ success

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

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Results — ✅ All selected jobs passed

Run: 28890370745
Workflow ref: fix/openclaw-config-perms-self-heal
Requested targets: shields-config
Requested jobs: (default — all default-enabled free-standing jobs; explicit-only jobs openshell-gateway-auth-contract, mcp-bridge-dev, hermes-gpu-startup, sandbox-rlimits-connect, and jetson-nvmap-gpu are skipped unless selected)
Summary: 1 passed, 0 failed, 0 cancelled, 0 skipped

Job Result
shields-config ✅ success

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Final-head maintainer disposition for c73ef23e576debd92a31010d5949bc4d7ff5e347.

Original-author credit

This remains Tinson Lai's PR: Tinson is the PR author and the author of the first six commits. No superseding PR was created. The repository currently permits squash merges only, so the final squash metadata will explicitly retain Co-authored-by: Tinson Lai <tinsonl@nvidia.com> in addition to the PR's existing authorship and DCO declaration.

Resolved review feedback

  • All CodeRabbit inline threads are resolved. The final non-inline test finding is addressed by c73ef23e: the sealed-state test is now root-gated, proves the exact two-file posture classifies as sealed, removes .config-hash, and proves the incomplete pair becomes indeterminate instead of passing without exercising the claim.
  • All 13 PR commits are GitHub Verified with reason valid; DCO, commit lint, and the repository hooks pass.
  • The focused integration pair passes: 24 passed, with 3 intentional root-only host skips. The root boundary is exercised in the production-image suite.
  • The final-head CI suite, including CodeQL, macOS, WSL, self-hosted production-image tests, and CodeRabbit, passes.
  • Exact-head live shields-config run 28890370745 passes. It performs a fresh install/onboard, verifies 2770/660 sandbox:sandbox, drives the documented Doctor path, verifies restoration to 2770/660, and proves a runtime write.
  • The cross-issue sweep found no additional adjacent fix or contradiction. #6300 is the only open issue claimed by this PR; #6047 remains closed and its repair contract is preserved.

Advisor dispositions

The following are explicit maintainer dispositions for the automated advisor's remaining items:

GPT-5.5

  • PRA-1, PRA-2, PRA-T1PRA-T7resolved or justified. The source audit records the exact invalid state, trusted source boundary, source-fix constraint, regression tests, and removal condition. Exact-head live run 28890370745 executes install.sh --fresh, starts the default OpenClaw sandbox, verifies .openclaw as 2770 sandbox:sandbox and both fixed files as 660 sandbox:sandbox, drives the documented Doctor collapse/repair path, and proves a runtime write. Seeded production-image tests separately prove exact recovery and rejection boundaries. The v0.0.73-to-v0.0.74 comparison and six-device list are historical issue evidence rather than a requirement to recreate retired releases on every device; current Linux-sandbox behavior is covered by the production image and live run, with macOS and WSL host E2E also green.

Nemotron Ultra

  • PRA-3, PRA-4, PRA-5override with source-boundary rationale. A repository-wide producer audit found no active path capable of the exact [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 signature: sandbox:sandbox 0755 parent, root:root 0700 config directory, and both fixed files as single-link root:root 0600. Active image, shields, and transaction paths produce distinguishable ownership or mode postures. The transaction guard's broader orphan-freeze discriminator is intentionally fail-safe for journal recovery; narrowing it would weaken that boundary, so startup revokes readiness and reclaims only the exact [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 signature before guard recovery. The compatibility repair documents the source-fix constraint and removal condition and rejects every broader state. Filing another issue without a reproducible producer, owner, or acceptance test would duplicate [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 speculatively; [All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode #6300 remains the evidence record and this PR supplies the permanent fresh-onboard plus fail-closed regressions.
  • PRA-1, PRA-T1, PRA-T6override the proposed “exactly one concurrent reclaim succeeds” contract. Reclaim mutates no content and applies the same idempotent ownership/mode transition to pinned, single-link inodes. Two callers that pin the original state may both safely complete; later callers fail classification. The valid invariant is a verified final 2770/660 sandbox:sandbox posture with no external alias mutation, not exactly one successful process. Production startup has one PID-1 caller after readiness is revoked.
  • PRA-2, PRA-T2, PRA-T7override the synthetic capture-versus-reclaim race. Capture requires a pinned sandbox-owned 2770/660 mutable tree; reclaim requires the mutually exclusive exact root-owned 0700/0600 posture. Production invokes these phases sequentially. Any raced identity or metadata transition is revalidated through pinned descriptors and fails closed.
  • PRA-6, PRA-11, PRA-13, PRA-T5 — the privileged recovery path is intentionally Linux-container-only. fd_mount_id() documents that contract and fails closed elsewhere. A non-root-owned directory classifies as indeterminate before mount-ID comparison, so the non-root developer path does not call it. macOS and WSL host E2E pass; this disposition does not claim that Darwin provides Linux /proc/self/fdinfo semantics.
  • PRA-7 — resolved in source and test: the resolver comment scopes every environment/checkout fallback to non-root developer/test harnesses, and production-image test 30f proves root refuses those fallbacks.
  • PRA-8, PRA-T4, PRA-T8 — production-image tests exercise the real kernel-backed owner-child credential and SCM_RIGHTS descriptor handoff. Test 30 covers the supervised handoff; 30c covers capture; 30d injects path replacement after owner normalization. A mocked unit would be weaker evidence for this boundary.
  • PRA-9, PRA-T3override with fail-closed availability rationale. Fixed files are handed off before the directory, and the directory is made inaccessible during final transfer. An interrupted mixed state no longer matches the exact reclaim signature; the next startup either completes the recognized guard recovery or refuses startup. There is no injected SIGKILL test, and this accepts an availability failure rather than any silent permission weakening.
  • PRA-10 — the 4097-byte read is a deliberate bounded parse of one pinned regular-file/directory fdinfo record, not the system mount table; overflow fails closed.
  • PRA-12 — the branch is mergeable against current main; overlapping open PRs touch different runtime/test hunks and present no semantic conflict with startup ordering or normalization.
  • PRA-15, PRA-17 — the immutable installed helper is the only root path. The environment, current-checkout, and script-relative paths are behind the non-root guard and serve distinct extracted-test and arbitrary-working-directory developer cases.
  • PRA-16 — the production-image cases intentionally extract different function sets and apply case-specific fault injection. A shared shell helper would add coupling without changing coverage or security behavior.
  • PRA-18 — the user-facing troubleshooting page documents the exact recovery and refusal boundary. The internal test identifier belongs in this validation record rather than operator documentation.
  • PRA-19 — test 30g's one-shot python3 interception is a deterministic fault-injection seam: it moves the path only after the real classifier returns, then asserts the pinned tree is untouched and the replacement is never mutated. Replacing it with a shared helper would not make that TOCTOU boundary more realistic or safer.

The historical v0.0.73-to-v0.0.74 comparison remains issue evidence; the merge gate is the current contract, which the fresh-onboard live run and production-image suite verify. These dispositions explicitly override the automated recommendations that were not implemented; no unresolved correctness or security finding remains.

@apurvvkumaria apurvvkumaria left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at c73ef23. Tinson Lai remains the PR author with his six original commits retained in the PR; all 13 commits are GitHub Verified and DCO is green. Final-head CI passes (40 pass, 2 expected skips), exact-head live shields-config run 28890370745 passes, all CodeRabbit threads are resolved, and c73ef23 addresses the final missing-fixed-file test gap. The remaining automated-advisor items are explicitly dispositioned with maintainer rationale in issuecomment-4907403981. #6300 is fixed and #6047's repair contract is preserved.

@apurvvkumaria apurvvkumaria merged commit 5b8e437 into main Jul 7, 2026
123 checks passed
@apurvvkumaria apurvvkumaria deleted the fix/openclaw-config-perms-self-heal branch July 7, 2026 19:00
@github-actions github-actions Bot added the v0.0.76 Release target label Jul 7, 2026
apurvvkumaria added a commit that referenced this pull request Jul 8, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Prepares the user-facing documentation for NemoClaw v0.0.76 and closes
the release-prep documentation gate. It adds the release highlights,
documents the arm64 Local NIM warning and expanded image cleanup
behavior, and fixes agent-specific command headings in generated guides.

## Changes

- Add the v0.0.76 release-notes section and move the shared-gateway
route containment entry out of the v0.0.74 history where it was
incorrectly placed.
- Document the advisory Linux arm64 Local NIM manifest warning in the
canonical platform matrix and local-inference guidance.
- Document that `gc` scans both gateway-built and locally prebuilt
sandbox image repositories.
- Keep OpenClaw and Hermes session headings out of the generated Deep
Agents command guide.
- Add a focused variant regression test for the agent-specific session
headings.

### Source summary

| Merged sources | Documentation coverage |
| --- | --- |
| [#6414](#6414),
[#6418](#6418),
[#6416](#6416),
[#6344](#6344) | v0.0.76 release
notes and the Deep Agents quickstart/inference routes |
| [#6340](#6340) | v0.0.76
release notes and existing Deep Agents observability guidance |
| [#6338](#6338),
[#6378](#6378),
[#6297](#6297) | v0.0.76 release
notes and existing inference/troubleshooting guidance |
| [#6362](#6362) | v0.0.76
release notes and existing lifecycle, command, and credential guidance |
| [#6330](#6330),
[#6307](#6307),
[#6008](#6008) | v0.0.76 release
notes and existing security, troubleshooting, and command guidance |
| [#6382](#6382) | v0.0.76
release notes and existing MCP/command guidance |
| [#6326](#6326),
[#5868](#5868),
[#5539](#5539) | v0.0.76 release
notes, platform matrix, inference options, and local-inference guidance
|
| [#6396](#6396),
[#6390](#6390),
[#6007](#6007) | v0.0.76 release
notes and existing messaging guidance |
| [#5388](#5388),
[#6249](#6249),
[#6303](#6303),
[#6306](#6306) | v0.0.76 release
notes and command/lifecycle guidance |

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

- [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:
- [ ] 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:

## Verification

- [x] PR description includes the DCO sign-off declaration 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 — `npx vitest run --project integration
test/generate-platform-docs.test.ts test/agent-variant-docs.test.ts
test/sync-agent-variant-docs.test.ts` (3 files, 29 tests passed)
- [ ] 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) —
completed with 0 errors and 2 pre-existing 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: Apurv Kumaria <akumaria@nvidia.com>


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

## Summary by CodeRabbit

* **Documentation**
* Added v0.0.76 release notes content, and removed an older conflicting
bullet from the surrounding release history.
* Expanded Local NVIDIA NIM guidance across inference/provider docs,
including an advisory for Linux arm64 DGX Spark/DGX Station hosts when a
matching `linux/arm64` image manifest is unavailable.
* Updated the command reference for correct session-section rendering
and clarified `gc` image cleanup sources.
* **Tests**
* Added coverage ensuring Deep Agents omits sessions headings while
Hermes includes them.
* **CI**
* Refreshed Local NVIDIA NIM provider notes used in the platform matrix.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
…VIDIA#6307)

Restore the mutable OpenClaw config ownership contract at startup while keeping sealed and ambiguous states fail-closed.

Retains Tinson Lai's original contribution and PR authorship.

Co-authored-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Prepares the user-facing documentation for NemoClaw v0.0.76 and closes
the release-prep documentation gate. It adds the release highlights,
documents the arm64 Local NIM warning and expanded image cleanup
behavior, and fixes agent-specific command headings in generated guides.

## Changes

- Add the v0.0.76 release-notes section and move the shared-gateway
route containment entry out of the v0.0.74 history where it was
incorrectly placed.
- Document the advisory Linux arm64 Local NIM manifest warning in the
canonical platform matrix and local-inference guidance.
- Document that `gc` scans both gateway-built and locally prebuilt
sandbox image repositories.
- Keep OpenClaw and Hermes session headings out of the generated Deep
Agents command guide.
- Add a focused variant regression test for the agent-specific session
headings.

### Source summary

| Merged sources | Documentation coverage |
| --- | --- |
| [NVIDIA#6414](NVIDIA#6414),
[NVIDIA#6418](NVIDIA#6418),
[NVIDIA#6416](NVIDIA#6416),
[NVIDIA#6344](NVIDIA#6344) | v0.0.76 release
notes and the Deep Agents quickstart/inference routes |
| [NVIDIA#6340](NVIDIA#6340) | v0.0.76
release notes and existing Deep Agents observability guidance |
| [NVIDIA#6338](NVIDIA#6338),
[NVIDIA#6378](NVIDIA#6378),
[NVIDIA#6297](NVIDIA#6297) | v0.0.76 release
notes and existing inference/troubleshooting guidance |
| [NVIDIA#6362](NVIDIA#6362) | v0.0.76
release notes and existing lifecycle, command, and credential guidance |
| [NVIDIA#6330](NVIDIA#6330),
[NVIDIA#6307](NVIDIA#6307),
[NVIDIA#6008](NVIDIA#6008) | v0.0.76 release
notes and existing security, troubleshooting, and command guidance |
| [NVIDIA#6382](NVIDIA#6382) | v0.0.76
release notes and existing MCP/command guidance |
| [NVIDIA#6326](NVIDIA#6326),
[NVIDIA#5868](NVIDIA#5868),
[NVIDIA#5539](NVIDIA#5539) | v0.0.76 release
notes, platform matrix, inference options, and local-inference guidance
|
| [NVIDIA#6396](NVIDIA#6396),
[NVIDIA#6390](NVIDIA#6390),
[NVIDIA#6007](NVIDIA#6007) | v0.0.76 release
notes and existing messaging guidance |
| [NVIDIA#5388](NVIDIA#5388),
[NVIDIA#6249](NVIDIA#6249),
[NVIDIA#6303](NVIDIA#6303),
[NVIDIA#6306](NVIDIA#6306) | v0.0.76 release
notes and command/lifecycle guidance |

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

- [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:
- [ ] 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:

## Verification

- [x] PR description includes the DCO sign-off declaration 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 — `npx vitest run --project integration
test/generate-platform-docs.test.ts test/agent-variant-docs.test.ts
test/sync-agent-variant-docs.test.ts` (3 files, 29 tests passed)
- [ ] 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) —
completed with 0 errors and 2 pre-existing 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: Apurv Kumaria <akumaria@nvidia.com>


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

## Summary by CodeRabbit

* **Documentation**
* Added v0.0.76 release notes content, and removed an older conflicting
bullet from the surrounding release history.
* Expanded Local NVIDIA NIM guidance across inference/provider docs,
including an advisory for Linux arm64 DGX Spark/DGX Station hosts when a
matching `linux/arm64` image manifest is unavailable.
* Updated the command reference for correct session-section rendering
and clarified `gc` image cleanup sources.
* **Tests**
* Added coverage ensuring Deep Agents omits sessions headings while
Hermes includes them.
* **CI**
* Refreshed Local NVIDIA NIM provider notes used in the platform matrix.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression integration: openclaw OpenClaw integration behavior NV QA Bugs found by the NVIDIA QA Team v0.0.76 Release target v0.0.77 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[All Platforms][Sandbox] /sandbox/.openclaw/ ownership changed to root:root — config files not writable in default shields-down mode

3 participants