Skip to content

fix(upgrade): protect legacy sandbox state - #7107

Closed
ericksoa wants to merge 1 commit into
mainfrom
fix/7073-legacy-upgrade-state
Closed

fix(upgrade): protect legacy sandbox state#7107
ericksoa wants to merge 1 commit into
mainfrom
fix/7073-legacy-upgrade-state

Conversation

@ericksoa

@ericksoa ericksoa commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Protect legacy sandbox state during installer-driven upgrades.
The upgrade now recovers sanitized managed-channel configuration that legacy registry rows cannot regenerate and stops before recreation when top-level /sandbox data falls outside the snapshot contract.

Related Issue

Fixes #7073

Changes

  • Restore a missing managed OpenClaw channel block, including Slack, from the sanitized pre-upgrade openclaw.json only during installer-driven legacy recreation.
    Freshly generated channel configuration still wins, and ordinary rebuilds retain the existing do-not-resurrect behavior.
  • Inspect top-level /sandbox entries after a successful strict pre-upgrade snapshot and fail before destructive recreation when unpreserved paths are present.
    NEMOCLAW_ACCEPT_UNPRESERVED_SANDBOX_DATA=1 explicitly acknowledges their loss without adding arbitrary, potentially credential-bearing data to the snapshot.
  • Preserve stopped-container backup retry behavior by running the new inspection only after the snapshot transport is usable.
  • Add focused merge, restore, finalization, installer, stopped-container, and fail-closed probe regressions plus operator documentation.

Root Cause and Detection Gap

Legacy registry rows predate persisted messaging intent, so recreation generated a fresh config without Slack and the normal selective merge correctly interpreted that omission as a removed managed channel.
Separately, the snapshot contract preserves declared agent state but did not surface arbitrary top-level /sandbox paths before the installer deleted the old sandbox.

Existing coverage proved normal rebuild ownership and manifest-declared restore behavior, but did not exercise an installer-selected legacy backup with missing messaging intent or undeclared top-level sandbox data.
The new tests cover both installer boundaries while retaining the ordinary rebuild behavior.

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: Maintainer-author review kept raw credentials excluded, retained fresh-config ownership, and made undeclared sandbox data fail closed before recreation.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — vitest CLI projects: 79 passed; installer integration: 2 passed; npm run typecheck passed.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

npm run docs completed with 0 errors and the repository's 2 current Fern warnings.


Signed-off-by: Aaron Erickson aerickson@nvidia.com

Summary by CodeRabbit

  • New Features

    • Improved pre-upgrade sandbox restoration by recovering missing managed messaging-channel settings while preserving newly generated configuration and sanitized credentials.
    • Added stricter checks for unpreserved /sandbox data before installer-driven upgrades.
    • Upgrades now stop when unpreserved paths are detected unless explicitly acknowledged with NEMOCLAW_ACCEPT_UNPRESERVED_SANDBOX_DATA=1.
  • Documentation

    • Updated backup, restore, and sandbox upgrade guidance with the new validation behavior and recovery options.

@ericksoa ericksoa self-assigned this Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Legacy sandbox upgrade preservation

Layer / File(s) Summary
Unpreserved sandbox backup gate
src/lib/state/user-managed-files-probe.ts, src/lib/actions/maintenance.ts, src/lib/actions/maintenance.test.ts, docs/manage-sandboxes/*.mdx
Strict pre-upgrade backups inspect top-level /sandbox entries, fail on probe errors or unacknowledged paths, and warn when NEMOCLAW_ACCEPT_UNPRESERVED_SANDBOX_DATA=1 is set.
Missing managed channel restoration
src/lib/state/openclaw-config-merge.ts, src/lib/state/openclaw-config-restore-input.ts, src/lib/state/state-file-restore.ts, src/lib/state/sandbox.ts, src/lib/state/*restore.test.ts
A restore option propagates through OpenClaw state restoration and restores missing managed channel blocks while retaining fresh generated configuration.
Pre-upgrade restore wiring
src/lib/onboard/created-sandbox-finalization.ts, src/lib/onboard/created-sandbox-finalization.test.ts, test/onboard-installer-restore-intent.test.ts
Pre-upgrade sandbox finalization enables missing managed channel restoration and verifies the option in restore-intent tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: bug-fix, area: sandbox, area: docs, v0.0.86

Suggested reviewers: cjagwani, laitingsheng

Sequence Diagram(s)

Strict pre-upgrade backup validation

sequenceDiagram
  participant Installer
  participant backupAll
  participant Sandbox
  Installer->>backupAll: run strict pre-upgrade backup
  backupAll->>Sandbox: probe top-level /sandbox entries
  Sandbox-->>backupAll: return unpreserved paths or probe error
  backupAll-->>Installer: continue, warn, or fail upgrade
Loading

Legacy OpenClaw restore

sequenceDiagram
  participant SandboxFinalization
  participant restoreRecreatedSandboxState
  participant restoreStateFile
  participant mergeOpenClawRestoredConfig
  SandboxFinalization->>restoreRecreatedSandboxState: restore pre-upgrade backup
  restoreRecreatedSandboxState->>restoreStateFile: pass restoreMissingManagedChannels
  restoreStateFile->>mergeOpenClawRestoredConfig: merge restored configuration
  mergeOpenClawRestoredConfig-->>restoreStateFile: retain fresh blocks and restore missing managed channels
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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 is concise and accurately summarizes the upgrade-focused sandbox protection changes.
Linked Issues check ✅ Passed The changes address #7073 by restoring missing managed channels and failing closed on unpreserved sandbox paths during installer upgrades.
Out of Scope Changes check ✅ Passed The diff stays focused on the documented upgrade recovery, restore, and backup safeguards, with tests and docs only.
✨ 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/7073-legacy-upgrade-state

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 17, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage remains at 96%, unchanged from the main branch.

TypeScript / code-coverage/cli

The overall coverage in the fix/7073-legacy-upgr... branch remains at 80%, unchanged from the main branch.

Show a code coverage summary of the most impacted files.
File main 027632a fix/7073-legacy-upgr... c7938a4 +/-
src/lib/state/u...-files-probe.ts 74% 65% -9%
src/lib/domain/.../connect-env.ts 97% 89% -8%
src/lib/state/o...config-merge.ts 96% 95% -1%
src/lib/agent/defs.ts 81% 81% 0%
src/lib/agent/s...store-reader.ts 90% 90% 0%
src/lib/sandbox...rce-identity.ts 91% 91% 0%
src/lib/state/o...estore-input.ts 93% 93% 0%
src/lib/actions/maintenance.ts 79% 83% +4%
src/lib/onboard...carryforward.ts 92% 96% +4%
src/lib/sandbox...vileged-exec.ts 75% 87% +12%

Updated July 17, 2026 12:41 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@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 `@src/lib/state/user-managed-files-probe.ts`:
- Around line 100-107: Restrict the `${configEntry}-data` exclusion in the probe
logic to the explicit legacy OpenClaw/Hermes agent or manifest contract, rather
than applying it to every agent. Keep the canonical config entry managed for all
applicable agents, and add a regression test proving a non-legacy agent’s
sibling data directory remains unmanaged and protected from deletion.
🪄 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: 79b6d0a1-bcca-4ae4-8813-ae1db62bf3f7

📥 Commits

Reviewing files that changed from the base of the PR and between aa3dd78 and c7938a4.

📒 Files selected for processing (15)
  • docs/manage-sandboxes/backup-restore.mdx
  • docs/manage-sandboxes/update-sandboxes.mdx
  • src/lib/actions/maintenance.test.ts
  • src/lib/actions/maintenance.ts
  • src/lib/onboard/created-sandbox-finalization.test.ts
  • src/lib/onboard/created-sandbox-finalization.ts
  • src/lib/state/openclaw-config-merge.test.ts
  • src/lib/state/openclaw-config-merge.ts
  • src/lib/state/openclaw-config-restore-input.ts
  • src/lib/state/sandbox-recreated-openclaw-restore.test.ts
  • src/lib/state/sandbox.ts
  • src/lib/state/state-file-restore.ts
  • src/lib/state/user-managed-files-probe.test.ts
  • src/lib/state/user-managed-files-probe.ts
  • test/onboard-installer-restore-intent.test.ts

Comment on lines +100 to +107
const configDir = path.posix.normalize(agent.configPaths.dir.replace(/\\/g, "/"));
if (path.posix.dirname(configDir) === USER_MANAGED_FILES_BASE) {
const configEntry = path.posix.basename(configDir);
managedEntries.add(configEntry);
// Older OpenClaw and Hermes images used a sibling durable data directory
// behind the canonical config path. It belongs to the same manifest-backed
// state and must not be reported as arbitrary root data.
managedEntries.add(`${configEntry}-data`);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not exclude <configEntry>-data for every agent.

Line 107 treats this sibling as managed even though the comment only establishes ownership for legacy OpenClaw and Hermes images. For another agent, user data with that name bypasses the strict gate and may be deleted during recreation.

Derive exclusions from an explicit agent or manifest contract, and add a non-legacy-agent regression test.

🤖 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/user-managed-files-probe.ts` around lines 100 - 107, Restrict
the `${configEntry}-data` exclusion in the probe logic to the explicit legacy
OpenClaw/Hermes agent or manifest contract, rather than applying it to every
agent. Keep the canonical config entry managed for all applicable agents, and
add a regression test proving a non-legacy agent’s sibling data directory
remains unmanaged and protected from deletion.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Informational

Advisor assessment: Informational / high confidence
Next action: Review the warnings below.
Findings: 0 blockers · 3 warnings · 0 suggestions
Status: Canonical ledger: 0 blocker(s), 3 warning(s), 0 suggestion(s).

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 3 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings differ; normalized E2E selections differ; Nemotron reported the same number of blockers, 3 fewer warnings, the same number of suggestions.

Nemotron output stays in workflow artifacts and does not change the assessment above.

E2E guidance

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

Recommended E2E: onboard-repair, onboard-resume, state-backup-restore, upgrade-stale-sandbox, cloud-onboard

3 optional E2E recommendations
  • openclaw-slack-pairing
  • credential-sanitization
  • ubuntu-repo-cloud-openclaw
3 warnings · 0 suggestions

Warnings

Warnings do not block.

PRA-1 Warning — Recheck the unpreserved-root boundary immediately before destructive recreation

  • Location: src/lib/actions/maintenance.ts:104
  • Category: correctness
  • Problem: The strict path snapshots and probes `/sandbox` once, then returns success to the installer. The probe result is not bound to the snapshot or revalidated before the installer destroys/recreates the sandbox. A process in the sandbox can create a new top-level path after this probe; that path was neither listed nor covered by the acknowledgement but can still be discarded by the subsequent recreation.
  • Impact: The new pre-upgrade data-loss guard can silently miss data created in the probe-to-recreation window, undermining the guarantee that every unpreserved path is reported before it is discarded.
  • Recommendation: Close the check-to-destruction window by rechecking the same boundary at the last destructive preflight (or otherwise quiescing/binding the sandbox state), and fail unless newly observed paths are explicitly acknowledged.
  • Verification: Inspect the installer path after `run_preupgrade_backup` and confirm whether it re-invokes `probeUnpreservedSandboxRootEntries` or otherwise prevents top-level `/sandbox` mutations before sandbox deletion.
  • Test coverage: Add a lifecycle regression that creates a new `/sandbox` top-level entry after the initial strict probe and verifies the destructive recovery is blocked or requires a renewed explicit acknowledgement.
  • Evidence: `backupAll` calls `probeUnpreservedSandboxRootEntries` only after a successful backup when strict mode is enabled (`src/lib/actions/maintenance.ts:104-113`). On success, `run_preupgrade_backup` returns to the installer (`scripts/install.sh:1974-1988`), which later performs the upgrade/recovery; the changed code contains no second root probe. The acknowledgement output enumerates only the initial `unpreservedRootEntries` (`src/lib/actions/maintenance.ts:114-118`).

PRA-2 Warning — Document the lifecycle and retirement condition for legacy channel recovery

  • Location: src/lib/state/sandbox.ts:175
  • Category: architecture
  • Problem: `restoreMissingManagedChannels` creates a compatibility exception that reintroduces a missing managed channel from a backup during pre-upgrade recovery. The comment identifies legacy rows as the invalid state and tests demonstrate the consumer, but neither the option nor the merge logic records why persisted messaging intent cannot be repaired at its source in this change or what condition permits removing this exception.
  • Impact: The exceptional ownership rule can become permanent and be reused beyond its intended legacy migration boundary, making it harder to reason about which channel state is authoritative during restore.
  • Recommendation: Document the source-fix constraint and a concrete removal condition adjacent to the compatibility option/merge exception, or replace the exception with a source migration if that is feasible in this PR.
  • Verification: Read `RecreatedSandboxRestoreOptions` and `mergeOpenClawChannels` and confirm they contain the legacy-state rationale but no source-fix constraint or retirement condition.
  • Test coverage: Add a contract test for the stated retirement boundary (for example, a registry row with persisted messaging intent must not enable legacy channel recovery) once the boundary is defined.
  • Evidence: `RecreatedSandboxRestoreOptions` says only that legacy upgrade rows lack persisted messaging intent (`src/lib/state/sandbox.ts:175-176`). The exception restores a missing managed channel whenever the internal flag is true (`src/lib/state/openclaw-config-merge.ts:97-110`). The new tests cover restoration but do not encode a migration/retirement boundary (`src/lib/state/openclaw-config-merge.test.ts:140-176`).

PRA-3 Warning — Fix the stopped-container ordering assertion

  • Location: src/lib/actions/maintenance.test.ts:365
  • Category: tests
  • Problem: The new ordering assertion reads `probeUnpreservedSandboxRootEntries.mock.invocationCallOrder[1]`, but the test invokes that mock only once. After `mockReset` in `beforeEach`, index 1 is undefined and the fallback becomes 0, so the assertion compares the backup call order against 0 and fails rather than validating that the backup precedes the probe.
  • Impact: The changed regression test cannot pass as written and does not protect the intended snapshot-before-probe ordering for stopped sandboxes.
  • Recommendation: Compare the first invocation of each mock (`invocationCallOrder[0]`) and retain an assertion that the probe was called exactly once if needed.
  • Verification: Read the test setup and invocation expectations: the test calls `backupStartedSandboxState` once and the changed implementation calls `probeUnpreservedSandboxRootEntries` once.
  • Test coverage: Keep a passing regression assertion that verifies a stopped sandbox completes its backup before the one strict root probe is performed.
  • Evidence: The assertion uses `mocks.probeUnpreservedSandboxRootEntries.mock.invocationCallOrder[1] ?? 0` (`src/lib/actions/maintenance.test.ts:365-367`). The test setup resets the probe mock before every test and this test does not call it directly; `backupAll` has one probe call per successfully backed-up sandbox (`src/lib/actions/maintenance.ts:104-110`). Therefore the relevant probe order is index 0, not index 1.

Workflow run details

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

@ericksoa ericksoa added v0.0.86 bug-fix PR fixes a bug or regression NV QA Bugs found by the NVIDIA QA Team area: install Install, setup, prerequisites, or uninstall flow area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: messaging Messaging channels, bridges, manifests, or channel lifecycle area: docs Documentation, examples, guides, or docs build integration: openclaw OpenClaw integration behavior integration: slack Slack integration or channel behavior labels Jul 17, 2026
@ericksoa

Copy link
Copy Markdown
Contributor Author

Closing in favor of #7093, which owns the issue-scoped warning fix and is already exact-head green and approved.

@ericksoa ericksoa closed this Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs Documentation, examples, guides, or docs build area: install Install, setup, prerequisites, or uninstall flow area: messaging Messaging channels, bridges, manifests, or channel lifecycle area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression integration: openclaw OpenClaw integration behavior integration: slack Slack integration or channel behavior NV QA Bugs found by the NVIDIA QA Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Linux][Upgrade] v0.0.55→v0.0.81 legacy-sandbox recovery drops Slack channel config + user data outside /sandbox/.openclaw

1 participant