Skip to content

fix(state): restore Hermes cron scripts before enabling restored jobs - #7880

Closed
laitingsheng wants to merge 34 commits into
mainfrom
fix/hermes-cron-script-state-restore
Closed

fix(state): restore Hermes cron scripts before enabling restored jobs#7880
laitingsheng wants to merge 34 commits into
mainfrom
fix/hermes-cron-script-state-restore

Conversation

@laitingsheng

@laitingsheng laitingsheng commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Hermes confines the scripts that no_agent cron jobs run to HERMES_HOME/scripts, but that directory was absent from the Hermes state contract. A rebuild could therefore restore enabled cron definitions without restoring the scripts they call. This PR includes scripts in Hermes state, publishes restored directories as complete units with cron after scripts, and rolls live state back if staged publication fails instead of leaving a partial restore or deleting the prior state.

Before either live scheduled-work directory moves, NemoClaw now asks the running Hermes gateway to enter its native external-drain state and waits for messaging, API, and cron activity to reach zero. It validates every enabled restored script reference before releasing only the drain marker NemoClaw created. Operator-owned drains remain in place, and rollback failure preserves both the recovery tree and the drain.

Related Issue

Fixes #7806

Changes

  • Add scripts to the Hermes state contract and the high-risk state-directory lock inventory.
  • Stage whole state directories before publication and apply scheduled-work definitions last.
  • Preserve each live directory in a rollback tree before replacing or removing it. Restore the prior directories when publication fails.
  • Preserve .nemoclaw-restore-rollback and refuse a later restore if rollback or post-commit cleanup cannot finish, so a retry cannot overwrite the recovery copy.
  • Quiesce Hermes through the pinned native drain marker before the first scripts/cron transition; wait for gateway_state=draining and zero active messaging, API, and cron work.
  • Validate enabled job records and require their script references to resolve to readable regular files inside HERMES_HOME/scripts before resuming dispatch. Hermes invokes those files through an explicit Bash/Python interpreter, so readability is the pinned runtime permission contract.
  • Preserve a drain already owned by an operator. Release only a correctly shaped NemoClaw ownership token, and fail closed with the drain engaged if rollback cannot restore the prior state.
  • Install the restore guard root-owned and immutable in the Hermes image and bind it to the reviewed source SHA-256.
  • Keep the image-managed OpenClaw extension merge path unchanged because it must combine fresh image content with backed-up user content.
  • Document Hermes script capture, drain/validation behavior, and recovery-tree handling.

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: Codex Desktop performed a serialized nine-category review of the exact current-main diff at 88f6b0cfa9c69976ba0df1b0fd0b0fad56c84b35. Secrets, input/path validation, authorization, dependencies, logging, cryptography, configuration scope, rollback/concurrency integrity, and regression coverage pass. The root-owned image helper is hash-bound; every dynamic shell path is quoted; enabled scripts must remain under the resolved scripts root; operator drains are not intentionally released; and failed rollback preserves the drain and recovery tree. The follow-up CI repair only adds the helper to Docker replay fixtures and classifies its SHA-256 build argument as an integrity pin. Exact diff fingerprint: d93aa2010392d6b11ba46b87da530791f32e03b997cd6743a92ff25718a272ea (git diff --binary origin/main...HEAD | shasum -a 256). Fresh exact-head CI/E2E and automated review remain required.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer reviewed the completed changes
  • Result: docs-updated
  • Evidence: docs/manage-sandboxes/backup-restore.mdx, docs/reference/commands.mdx, and docs/security/best-practices.mdx; the exact-head CI repair is test/contract-only and needs no additional docs. npm run docs completed with 0 errors and 2 pre-existing warnings.
  • Agent: Codex Desktop (serialized main task)

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit: Not applicable; scripts/prepare-dgx-station-host.sh is unchanged.
  • Station profile/scenario: Not applicable.
  • Result: Not applicable.
  • Supporting evidence: Not applicable.

Verification

  • PR description includes a Signed-off-by: line and every new commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, and npm run validate:pr passed against current origin/main
  • Targeted behavior tests pass for the current change set — staged Hermes restore: 15/15; restore guard/image/watch contracts: 22/22; adjacent CLI restore suites: 21/21; broader snapshot/recreation suites: 102 passed, 2 skipped; state permission/guard suites: 37 passed, 1 skipped. Exact CI repair: affected Docker replay assertions pass, managed startup profile 102/102, Biome, npm run checks, and full npm run validate:pr pass. The config-hash path also passes 3/3 in a clean Linux container with isolated PyYAML; the macOS-only permission result is an environment limitation, not a product failure.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Fresh exact-head GitHub CI, E2E, CodeRabbit, and Advisor gates are running.
  • 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 and 2 pre-existing Fern warnings
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

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

Summary by CodeRabbit

  • New Features

    • Hermes scheduled-work backups and restores now include associated scripts and cron definitions.
    • Restore operations safely drain active gateway activity and validate script references before completion.
  • Bug Fixes

    • Improved transactional rollback and cleanup when publication or validation fails.
    • Added safeguards against unsafe script paths and unauthorized state-directory changes.
    • Failed recoveries retain rollback information to prevent unsafe subsequent restores.
  • Documentation

    • Updated backup, restore, command-reference, and security guidance to reflect the enhanced safeguards.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5b18fbbc-dc29-480c-ab43-62847ee0d4ba

📥 Commits

Reviewing files that changed from the base of the PR and between 61b91a3 and 1e263e6.

📒 Files selected for processing (6)
  • agents/hermes/Dockerfile
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx
  • docs/security/best-practices.mdx
  • src/lib/state/sandbox.ts
  • test/hermes-final-image-layout.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • test/hermes-final-image-layout.test.ts
  • docs/reference/commands.mdx
  • docs/security/best-practices.mdx
  • docs/manage-sandboxes/backup-restore.mdx
  • agents/hermes/Dockerfile
  • src/lib/state/sandbox.ts

📝 Walkthrough

Walkthrough

Hermes now persists .hermes/scripts with cron state. Restore drains gateway work, validates enabled scripts, stages directory replacement, publishes scripts before cron, and preserves rollback and cleanup state.

Changes

Sandbox restore flow

Layer / File(s) Summary
Hermes state and runtime contract
agents/hermes/manifest.yaml, agents/hermes/Dockerfile, src/lib/onboard/managed-startup/profile.ts, src/lib/shields/state-dir-lock.ts, scripts/state-dir-guard.py, test/*
Hermes declares scripts as state. The runtime packages and verifies the restore guard. Protected-file, ownership, mode, image-layout, and high-risk state checks include the new guard and state directory.
Gateway drain and script validation
agents/hermes/restore-cron-guard.py, test/hermes-restore-cron-guard.test.ts, docs/security/best-practices.mdx
Restore drains gateway work, validates enabled cron script references, preserves operator-owned drains, and fails closed when validation fails.
Staged state publication
src/lib/state/sandbox.ts, src/lib/state/sandbox-staged-restore.test.ts, docs/manage-sandboxes/backup-restore.mdx, docs/reference/commands.mdx
Restore stages directory replacements, publishes scripts before cron, rolls back failed transitions, and preserves unrecovered recovery state.
Validation and watch coverage
test/helpers/vitest-watch-triggers.ts, test/vitest-watch-triggers.test.ts
Vitest watch triggers map changes to the restore guard to its dedicated test suite.

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

Suggested labels: area: security

Sequence Diagram(s)

sequenceDiagram
  participant SandboxRestore
  participant RestoreCronGuard
  participant HermesGateway
  participant HermesState
  SandboxRestore->>RestoreCronGuard: Begin drain
  RestoreCronGuard->>HermesGateway: Stop dispatch and wait for active work
  SandboxRestore->>HermesState: Stage scripts and cron state
  SandboxRestore->>RestoreCronGuard: Validate enabled script references
  RestoreCronGuard-->>SandboxRestore: Allow publication or fail closed
  SandboxRestore->>HermesState: Publish scripts before cron
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: restoring Hermes cron scripts before enabling restored jobs.
Linked Issues check ✅ Passed The PR restores scripts and cron definitions transactionally, pauses scheduling, validates enabled scripts, and resumes only after commit or rollback [#7806].
Out of Scope Changes check ✅ Passed The changes support transactional Hermes restoration, drain safety, integrity controls, documentation, and related test coverage.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hermes-cron-script-state-restore

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

@github-actions

Copy link
Copy Markdown
Contributor

@github-code-quality

github-code-quality Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 61b91a3 in the fix/hermes-cron-scri... branch remains at 96%, unchanged from commit 9b6b8df in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit 61b91a3 in the fix/hermes-cron-scri... branch remains at 81%, unchanged from commit fdd6828 in the main branch.

Show a code coverage summary of the most impacted files.
File main fdd6828 fix/hermes-cron-scri... 61b91a3 +/-
src/lib/onboard...aging-config.ts 100% 90% -10%
src/lib/onboard...hannel-setup.ts 89% 83% -6%
src/lib/messagi...annel-config.ts 96% 92% -4%
src/lib/onboard...point-record.ts 100% 97% -3%
src/lib/onboard...ox-messaging.ts 91% 89% -2%
src/lib/state/sandbox.ts 85% 86% +1%
src/lib/onboard...est-fixtures.ts 93% 96% +3%
src/lib/onboard...ing-selector.ts 78% 82% +4%
src/lib/state/r...ry-messaging.ts 48% 55% +7%
src/lib/messagi...an-authority.ts 0% 100% +100%

Updated August 05, 2026 06:46 UTC

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 1 blocker · 11 warnings · 0 suggestions
  • Model comparison: normalized findings differ; normalized terminology decisions differ; normalized E2E selections differ; Nemotron reported 1 more blocker, 11 more warnings, the same number of suggestions.
7 terminology differences from the second opinion

Advisory only. These are normalized differences from the primary terminology receipt.

  • scheduled-work at agents/hermes/restore-cron-guard.py:4: selected only by the second-opinion lane as established.
  • quiesce at src/lib/state/sandbox.ts:1681: selected only by the second-opinion lane as established.
  • script directory at src/lib/state/sandbox-staged-restore.test.ts:318: selected only by the second-opinion lane as established.
  • drain marker at agents/hermes/restore-cron-guard.py:59: selected only by the second-opinion lane as define.
  • staged restore at src/lib/state/sandbox-staged-restore.test.ts:65: selected only by the second-opinion lane as define.
  • ownership token at agents/hermes/restore-cron-guard.py:241: selected only by the second-opinion lane as define.
  • restore guard at agents/hermes/restore-cron-guard.py:29: selected only by the second-opinion lane as define.

Second-opinion terminology and E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate.

4 semantic terminology decisions

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

  • established — drain at docs/manage-sandboxes/backup-restore.mdx:54: Retain `drain` for the Hermes gateway quiescence state.
  • justified — operator-owned drain at test/hermes-restore-cron-guard.test.ts:149: Retain the modifier where release authority must be distinguished.
  • justified — scheduled work at agents/hermes/restore-cron-guard.py:4: Retain `scheduled-work` for the restore ordering and quiescence boundary.
  • define — recovery tree at src/lib/state/sandbox.ts:1785: Keep the path and recovery purpose adjacent when referring to the recovery tree.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: cloud-inference, cloud-onboard, full-e2e, hermes-e2e, hermes-inference-switch, managed-image-multiarch-startup, security-posture, device-auth-health, issue-4462-scope-upgrade-approval, onboard-repair, onboard-resume, openclaw-inference-switch

1 optional E2E recommendation
  • state-backup-restore

Workflow run details

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

Signed-off-by: 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: 2

🤖 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 `@src/lib/state/sandbox-staged-restore.test.ts`:
- Around line 46-48: Update the fixture setup in the sandbox staged-restore test
to create the scripts directory if necessary and always write digest.sh,
removing the options.stateDirs.includes("scripts") conditional. Leave archive
selection controlled by the manifest.

In `@src/lib/state/sandbox.ts`:
- Around line 1377-1388: Update the restore command construction around the
commands array so staging cleanup runs when extraction or any state-directory
move fails, not only on successful completion. Add failure-safe shell cleanup,
such as an exit trap, for the staging path while preserving the existing
successful restore flow, and add coverage for failed extraction or move cleanup.
🪄 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: 00a0a7d7-3a7b-414a-96ef-f29fbed89c70

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcb89e and 6462e22.

📒 Files selected for processing (5)
  • agents/hermes/manifest.yaml
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx
  • src/lib/state/sandbox-staged-restore.test.ts
  • src/lib/state/sandbox.ts

Comment thread src/lib/state/sandbox-staged-restore.test.ts Outdated
Comment thread src/lib/state/sandbox.ts Outdated

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

♻️ Duplicate comments (1)
src/lib/state/sandbox-staged-restore.test.ts (1)

42-46: ⚠️ Potential issue | 🟠 Major

Still create backup/scripts before the unconditional write.

Lines 42-44 create only configured state directories, so Line 46 throws ENOENT whenever stateDirs omits "scripts". Create the fixture directory independently; let the manifest control whether it is restored.

Proposed fix
     for (const stateDir of options.stateDirs) {
       fs.mkdirSync(path.join(backupPath, stateDir), { recursive: true });
     }
+    fs.mkdirSync(path.join(backupPath, "scripts"), { recursive: true });
     fs.writeFileSync(path.join(backupPath, "cron", "jobs.json"), '{"jobs":[{"enabled":true}]}\n');
     fs.writeFileSync(path.join(backupPath, "scripts", "digest.sh"), "#!/bin/bash\necho ok\n");

This is the same unresolved fixture-setup issue reported previously.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/state/sandbox-staged-restore.test.ts` around lines 42 - 46, Update
the fixture setup around the stateDirs loop and scripts/digest.sh write to
always create the backup/scripts directory independently before writing the
file. Keep stateDirs controlling only which directories are configured for
restoration, while ensuring the unconditional scripts fixture write cannot fail
when scripts is omitted.
🤖 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.

Duplicate comments:
In `@src/lib/state/sandbox-staged-restore.test.ts`:
- Around line 42-46: Update the fixture setup around the stateDirs loop and
scripts/digest.sh write to always create the backup/scripts directory
independently before writing the file. Keep stateDirs controlling only which
directories are configured for restoration, while ensuring the unconditional
scripts fixture write cannot fail when scripts is omitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ead92611-e2b1-4770-b6af-cfc925049245

📥 Commits

Reviewing files that changed from the base of the PR and between 6462e22 and 7da56a0.

📒 Files selected for processing (1)
  • src/lib/state/sandbox-staged-restore.test.ts

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

Caution

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

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

1377-1390: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make failed publication rollback-safe, not only staging-clean.

The command still removes each live state directory before moving its staged replacement. If a mv fails, the new EXIT trap removes staging but cannot restore the live directory already deleted; earlier directories may also have been published. A transient publication failure can therefore leave the sandbox partially restored or missing state. Use a rollback/transactional swap and extend the failure test to seed existing state and assert it survives.

As per path instructions, destructive lifecycle operations must validate before mutation, preserve state/backup invariants, and cover failure/recovery/rebuild/resume behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/state/sandbox.ts` around lines 1377 - 1390, Update the restore
command construction around orderStateDirsForRestore so publication is
rollback-safe: preserve each existing live state directory in a backup, publish
staged replacements transactionally, and restore the original state if any
removal or move fails before cleanup. Keep staging cleanup via the EXIT trap,
and extend the failure test to seed existing state and verify it remains intact
after a failed publication.

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 `@src/lib/state/sandbox-staged-restore.test.ts`:
- Around line 80-83: The movesFail shim in the staged-restore test must record
that publication was attempted before exiting. Update the injected script in the
movesFail branch to write a failure marker, then assert that marker together
with success === false and staging cleanup in the affected test cases.

---

Outside diff comments:
In `@src/lib/state/sandbox.ts`:
- Around line 1377-1390: Update the restore command construction around
orderStateDirsForRestore so publication is rollback-safe: preserve each existing
live state directory in a backup, publish staged replacements transactionally,
and restore the original state if any removal or move fails before cleanup. Keep
staging cleanup via the EXIT trap, and extend the failure test to seed existing
state and verify it remains intact after a failed publication.
🪄 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: 9b7dc759-dc37-43fb-99aa-9c06ac5ca2bb

📥 Commits

Reviewing files that changed from the base of the PR and between 7da56a0 and 617ea5d.

📒 Files selected for processing (2)
  • src/lib/state/sandbox-staged-restore.test.ts
  • src/lib/state/sandbox.ts

Comment thread src/lib/state/sandbox-staged-restore.test.ts Outdated
@laitingsheng laitingsheng added NV QA Bugs found by the NVIDIA QA Team integration: hermes Hermes integration behavior area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression labels Jul 30, 2026
@cjagwani

Copy link
Copy Markdown
Collaborator

Babysitting review for exact head dfde003a364d9ecf8ffd1f68097dea852d9c8e38 (plain comment; no Changes Requested review): approval is held on one code issue plus the exact-head protected failure.

  • buildStagedRestoreCommand removes each live state directory before moving its staged replacement. If any mv fails, the EXIT trap removes staging but cannot restore the live directory already deleted, and any earlier directories remain replaced. The new failure test confirms this partial-publication behavior (scripts is attempted, cron remains absent) but does not seed/assert preservation of pre-restore state. Because this is a destructive lifecycle path, publication needs a rollback-safe swap: retain each old directory until all new directories are publishable, and on failure restore every prior live directory. Add a failure test with existing scripts/cron state that proves the original state survives and the gateway never sees restored cron before its script.

  • The trusted exact-head E2E gate is terminal red: selected run 30524999509 failed hermes-e2e while building the Hermes image because /sandbox/.hermes/runtime/cron-executions.db was absent at Dockerfile step 137. The gate policy does not permit retrying a product failure on the same SHA. Please determine whether the head caused/exposed it and fix it; if it is independently proven infrastructure/base behavior, record a narrowly scoped maintainer waiver with that evidence.

No base refresh is requested.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria apurvvkumaria self-assigned this Jul 30, 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.

Caution

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

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

1352-1393: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make staged publication rollback-safe.

Removing a live directory before its staged mv succeeds turns a failed restore into data loss; the EXIT trap only removes staging. Preserve every replaced live directory in a rollback location, restore all prior directories if any publish step fails, and delete rollback data only after the full ordered publication succeeds. Add failed-mv coverage proving existing scripts and cron survive and restored cron is never exposed without its scripts.

As per path instructions, destructive lifecycle operations must preserve state/backup invariants and cover failure, recovery, rebuild, and resume behavior without bypassing the public action boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/state/sandbox.ts` around lines 1352 - 1393, Update
buildStagedRestoreCommand and its restore tests to make publication
rollback-safe: move each existing live state directory into a rollback location
before replacing it, and on any failed publish restore all previously replaced
directories in the original order. Remove rollback data only after every ordered
directory publish succeeds, while retaining staging cleanup. Add failed-mv
coverage proving existing scripts and cron are preserved and cron is never
visible without its restored scripts.

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.

Outside diff comments:
In `@src/lib/state/sandbox.ts`:
- Around line 1352-1393: Update buildStagedRestoreCommand and its restore tests
to make publication rollback-safe: move each existing live state directory into
a rollback location before replacing it, and on any failed publish restore all
previously replaced directories in the original order. Remove rollback data only
after every ordered directory publish succeeds, while retaining staging cleanup.
Add failed-mv coverage proving existing scripts and cron are preserved and cron
is never visible without its restored scripts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 67464f1b-80a9-46ba-bb13-4d77aee86011

📥 Commits

Reviewing files that changed from the base of the PR and between dfde003 and a5a1de4.

📒 Files selected for processing (3)
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx
  • src/lib/state/sandbox.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
…edger-e2e

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

Blocking: current head 7824b3f is syntactically invalid. The merge-resolution change removed the closing brace of buildStagedRestoreCommand in src/lib/state/sandbox.ts after its return and before snapshotManifestAuthority. Parsing the exact blob reaches EOF with TypeScript diagnostic TS1005 at 2615:1: closing brace expected, so the CLI cannot type-check or build and the feature cannot serve its stated purpose.\n\nPlease restore the missing closing brace and run npm run typecheck:cli plus the staged-restore tests and required CI. This is a direct, reproducible current-revision build break.

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

Re-reviewed exact head a5a698f. The blocking syntax defect remains in src/lib/state/sandbox.ts: buildStagedRestoreCommand closes its return expression with ); but never closes the function body before function snapshotManifestAuthority begins. TypeScript therefore parses the following declarations inside a function and the supported CLI build/typecheck cannot succeed. Please add the missing closing brace immediately after the return expression and add or run a focused syntax/typecheck regression on this exact revision. No other blocking finding is asserted here.

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

🤖 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 `@agents/hermes/restore-cron-guard.py`:
- Around line 108-139: Update validate_enabled_scripts to validate each enabled
script’s readability using the gateway user’s permissions rather than os.access
under the restore process; perform the open/readability check through the
existing privilege-switching mechanism and retain the current missing/unreadable
ValueError behavior. Add a negative test covering a sandbox-owned 0600 script
that the restore process can access but gateway cannot.
- Around line 50-84: Update begin_drain and _release_owned_marker to use atomic
create-if-absent and clear-if-token-matches operations. Always acquire an owned
drain marker before checking or waiting on the gateway PID, including when it is
initially inactive, and return “inactive” only after safely releasing that owned
marker. Ensure the owned token is released on successful completion as well as
timeout, commit, or rollback, while never clearing a replacement marker.

In `@docs/security/best-practices.mdx`:
- Line 347: In the Hermes rebuild restore description, split the two sentences
onto separate source lines while preserving their wording and paragraph
formatting.
🪄 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: bdce40c8-3a99-4ed7-aab7-38e057a478e2

📥 Commits

Reviewing files that changed from the base of the PR and between dfde003 and a5a698f.

📒 Files selected for processing (7)
  • agents/hermes/Dockerfile
  • agents/hermes/manifest.yaml
  • agents/hermes/restore-cron-guard.py
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx
  • docs/security/best-practices.mdx
  • scripts/state-dir-guard.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • agents/hermes/manifest.yaml
  • docs/reference/commands.mdx

Comment thread agents/hermes/restore-cron-guard.py
Comment thread agents/hermes/restore-cron-guard.py
Comment thread docs/security/best-practices.mdx Outdated
laitingsheng and others added 4 commits August 4, 2026 06:26
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Claim an exclusive restore drain marker before the gateway PID check so a
gateway that starts during publication still sees the drain, release only a
marker the restore owns, and validate enabled cron scripts against the gateway
account rather than the restore process.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@cjagwani

cjagwani commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Exact-head CI follow-up for 2cd6d4ed8: the Hermes managed-image job fails deterministically before startup because agents/hermes/restore-cron-guard.py no longer matches NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256 in the image build. Buildx stops at the sha256sum -c - guard with the explicit hash-mismatch error.

Please update the committed Hermes restore-guard digest to the exact current script bytes (and its contract assertion, if applicable), then let the new head rerun the managed-image and protected E2E gates. I have not edited the author branch or rerun the failing SHA.

@github-actions github-actions Bot added v0.0.103 Release target and removed v0.0.102 labels Aug 4, 2026
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
The image build and the final-image layout contract both compare
agents/hermes/restore-cron-guard.py against the committed digest, and the
pinned value still described an earlier revision of the script, so buildx
stopped at the sha256sum guard before startup.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
agents/hermes/restore-cron-guard.py (1)

55-59: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Make drain-marker ownership operations atomic.

Lines 96-100 can overwrite an operator drain that appears after drain_requested(). Lines 55-59 can clear an operator drain that replaces this restore marker after the ownership check. The local ownership file does not protect the shared gateway marker.

Add create-if-absent and clear-if-token-matches operations to drain_control. Add an interleaving test for both replacement races. This violates the required operator-drain preservation.

Also applies to: 96-100

🤖 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 `@agents/hermes/restore-cron-guard.py` around lines 55 - 59, Make drain-marker
ownership atomic across the restore flow: add drain_control operations that
create the shared marker only if absent and clear it only when its token still
matches, then update the marker acquisition around drain_requested() and
_release_owned_marker to use them. Ensure operator drains inserted between
either ownership check and mutation are preserved, and add interleaving coverage
for both replacement races.
🧹 Nitpick comments (2)
src/lib/state/sandbox-staged-restore.test.ts (1)

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

Make the unset failure-target case explicit.

If options.failPublishingDir is undefined, line 160 evaluates target.endsWith("/"). This is false for current production targets, so the shim behaves correctly today. The guard depends on targets never carrying a trailing slash.

Compare against a sentinel that cannot match instead.

♻️ Proposed change
-if (
-  ${JSON.stringify(options.movesFail === true)} ||
-  (isPublish && target.endsWith("/" + ${JSON.stringify(options.failPublishingDir ?? "")})) ||
-  (isRollback && target.endsWith("/" + ${JSON.stringify(options.failRollingBackDir ?? "")}))
-) process.exit(1);
+const failPublishingDir = ${JSON.stringify(options.failPublishingDir ?? null)};
+const failRollingBackDir = ${JSON.stringify(options.failRollingBackDir ?? null)};
+if (
+  ${JSON.stringify(options.movesFail === true)} ||
+  (isPublish && failPublishingDir !== null && target.endsWith("/" + failPublishingDir)) ||
+  (isRollback && failRollingBackDir !== null && target.endsWith("/" + failRollingBackDir))
+) process.exit(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/state/sandbox-staged-restore.test.ts` around lines 158 - 162, Update
the failure-target checks in the generated shim around options.failPublishingDir
and options.failRollingBackDir so an unset directory compares against an
explicitly non-matching sentinel instead of constructing a trailing-slash
suffix. Preserve the existing publishing and rollback failure behavior when a
directory is configured.
test/helpers/vitest-watch-triggers.ts (1)

87-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test/hermes-final-image-layout.test.ts to this trigger. It compares the SHA-256 of agents/hermes/restore-cron-guard.py with NEMOCLAW_HERMES_RESTORE_CRON_GUARD_SHA256; keep test/hermes-restore-cron-guard.test.ts and update its watch-trigger expectation.

🤖 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/helpers/vitest-watch-triggers.ts` around lines 87 - 90, Add
test/hermes-final-image-layout.test.ts to the restore-cron-guard entry in the
watch-trigger configuration, while retaining
test/hermes-restore-cron-guard.test.ts. Update the corresponding watch-trigger
expectation so both tests run when agents/hermes/restore-cron-guard.py changes.
🤖 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 `@agents/hermes/restore-cron-guard.py`:
- Around line 148-160: Update _readable_by_gateway() to validate gateway search
(execute) permission on every directory from scripts_dir through
script_path.parent, in addition to the script file’s read permission. Preserve
the existing identity and ownership checks, and add a nested 0700-directory test
covering the scheduler’s validation path.

In `@src/lib/state/sandbox-staged-restore.test.ts`:
- Around line 448-459: Update the test case “rejects an enabled restored job
whose script is absent” to assert the guard-event sequence used by its sibling
test around line 441, specifically proving that the validate guard rejects the
missing restored script. Keep the existing rollback and state-preservation
assertions unchanged.

In `@test/hermes-restore-cron-guard.test.ts`:
- Line 29: Update the parent test suite title in the “Hermes restore cron guard”
describe block to append the linked issue suffix “(`#7806`)”, leaving the child
test titles unchanged.
- Around line 192-214: Update the acceptance test around runGuardModule and the
digest.sh fixture so its readability check is independent of the host account:
either stub _gateway_identity() explicitly to the test user or make the fixture
searchable by gateway and change digest.sh to a gateway-readable mode. Preserve
the existing enabled/disabled job assertions and successful validation outcome.

---

Duplicate comments:
In `@agents/hermes/restore-cron-guard.py`:
- Around line 55-59: Make drain-marker ownership atomic across the restore flow:
add drain_control operations that create the shared marker only if absent and
clear it only when its token still matches, then update the marker acquisition
around drain_requested() and _release_owned_marker to use them. Ensure operator
drains inserted between either ownership check and mutation are preserved, and
add interleaving coverage for both replacement races.

---

Nitpick comments:
In `@src/lib/state/sandbox-staged-restore.test.ts`:
- Around line 158-162: Update the failure-target checks in the generated shim
around options.failPublishingDir and options.failRollingBackDir so an unset
directory compares against an explicitly non-matching sentinel instead of
constructing a trailing-slash suffix. Preserve the existing publishing and
rollback failure behavior when a directory is configured.

In `@test/helpers/vitest-watch-triggers.ts`:
- Around line 87-90: Add test/hermes-final-image-layout.test.ts to the
restore-cron-guard entry in the watch-trigger configuration, while retaining
test/hermes-restore-cron-guard.test.ts. Update the corresponding watch-trigger
expectation so both tests run when agents/hermes/restore-cron-guard.py changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f077068a-46d4-483d-a08a-57959952d9af

📥 Commits

Reviewing files that changed from the base of the PR and between 15b0c55 and 6a74ed6.

📒 Files selected for processing (20)
  • agents/hermes/Dockerfile
  • agents/hermes/manifest.yaml
  • agents/hermes/restore-cron-guard.py
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/reference/commands.mdx
  • docs/security/best-practices.mdx
  • scripts/state-dir-guard.py
  • src/lib/onboard/managed-startup/profile.ts
  • src/lib/shields/state-dir-lock.ts
  • src/lib/state/sandbox-staged-restore.test.ts
  • src/lib/state/sandbox.ts
  • test/helpers/vitest-watch-triggers.ts
  • test/hermes-doctor-config-hash.test.ts
  • test/hermes-final-image-layout.test.ts
  • test/hermes-restore-cron-guard.test.ts
  • test/sandbox-provisioning.test.ts
  • test/sandbox-rlimit-hooks.test.ts
  • test/shields-up-runtime-perms.test.ts
  • test/state-dir-guard.test.ts
  • test/vitest-watch-triggers.test.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/lib/onboard/managed-startup/profile.ts
  • scripts/state-dir-guard.py
  • agents/hermes/manifest.yaml
  • test/hermes-final-image-layout.test.ts
  • docs/security/best-practices.mdx
  • docs/reference/commands.mdx
  • test/sandbox-provisioning.test.ts
  • agents/hermes/Dockerfile
  • src/lib/state/sandbox.ts
  • test/hermes-doctor-config-hash.test.ts
  • docs/manage-sandboxes/backup-restore.mdx

Comment thread agents/hermes/restore-cron-guard.py Outdated
Comment thread src/lib/state/sandbox-staged-restore.test.ts
Comment thread test/hermes-restore-cron-guard.test.ts Outdated
Comment thread test/hermes-restore-cron-guard.test.ts
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@cv
cv dismissed stale reviews from apurvvkumaria and apurvvkumaria August 5, 2026 05:31

Dismissed as stale because commit 6949d3e restored the missing buildStagedRestoreCommand closing brace, and the current head passes the CLI build/typecheck path. This dismissal addresses only the obsolete syntax defect; newer review threads and check status remain independent.

cv and others added 5 commits August 4, 2026 23:12
The restore guard only checked the script's own permission bits, so a script
inside a directory the gateway cannot search passed validation and then failed
when the scheduler opened it. Validation now walks every directory from the
scripts root to the script's parent, and the pinned image digest follows the
changed script.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@cjagwani

cjagwani commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closing as superseded by #7871, which merged at exact head 472b9a566a as ead85bcda8. The maintainer decision on #7806 explicitly selected #7871 as the merge candidate and directed that #7880 be closed only after that merge. Thank you for the contribution and the alternative implementation.

@cjagwani cjagwani closed this Aug 5, 2026
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: hermes Hermes integration behavior v0.0.103 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ubuntu 24.04][Upgrade] rebuild enables restored cron jobs before their scripts are available

7 participants