Skip to content

fix(cli): preserve manually-added policy presets across sandbox rebuild#2023

Open
ericksoa wants to merge 2 commits intomainfrom
fix/1952-rebuild-restore-policy-presets
Open

fix(cli): preserve manually-added policy presets across sandbox rebuild#2023
ericksoa wants to merge 2 commits intomainfrom
fix/1952-rebuild-restore-policy-presets

Conversation

@ericksoa
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa commented Apr 17, 2026

Summary

  • Fixes [NemoClaw][brev]nemoclaw rebuild does not restore Telegram (messaging) policy presets — bridge starts but cannot reach api.telegram.org #1952: After nemoclaw <name> rebuild --yes, policy presets added via policy-add (e.g. telegram) were lost because they only existed in the registry's sandbox entry, not in the onboard session. The sandbox deletion wiped them before the resume path could replay them.
  • sandboxRebuild() now reads applied presets from the registry before deletion and merges them (with dedup via Set) into the session's policyPresets, so the onboard resume path replays the full set.
  • If the sandbox is degraded and getAppliedPresets fails, rebuild falls back to session presets only (logged warning, no abort).

Test plan

  • New test: merges applied presets into session with deduplication
  • New test: handles session with no prior policyPresets
  • New test: no-op when no presets are applied
  • New test: graceful fallback when getAppliedPresets throws (degraded sandbox)
  • New test: full dedup when presets overlap between session and applied
  • Manual: onboard, policy-add telegram, rebuild --yes — verify Telegram bridge egress works after rebuild

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Sandbox rebuild now preserves and merges policy presets into the resumed session, with deduplication and resilience if preset data cannot be read.
  • Tests

    • Added end-to-end tests covering preset merging, deduplication, initialization when absent, no-op when none applied, and error-tolerance during rebuild.

…ld (Fixes #1952)

During rebuild, policy presets added via `policy-add` after initial onboard
were lost because they only existed in the registry's sandbox entry, not in
the onboard session. The sandbox deletion wiped them before the resume path
could replay them.

Now sandboxRebuild() reads applied presets from the registry before deletion
and merges them into the session's policyPresets (with dedup). If the sandbox
is degraded and getAppliedPresets fails, rebuild falls back to session presets
only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ericksoa ericksoa self-assigned this Apr 17, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Added merging of sandbox-applied policy presets into the onboard session during the rebuild flow, implemented a new helper to perform the merge, and added tests that validate merging, deduplication, error handling, and initialization of session presets.

Changes

Cohort / File(s) Summary
Rebuild flow change
src/nemoclaw.ts
Inserted a new Step 2b in sandboxRebuild that calls policies.mergePresetsIntoSession(sandboxName, onboardSession, log) after workspace backup and before sandbox deletion to merge applied presets into the onboard session.
Policy helper
src/lib/policies.ts
Added exported mergePresetsIntoSession(sandboxName, sessionMod, log = () => {}) which reads applied presets from the registry, logs them, merges them into sessionMod with deduplication, logs the resulting session presets, and catches/logs errors without aborting.
Tests
test/rebuild-policy-presets.test.ts
New Vitest suite exercising mergePresetsIntoSession behavior: merging with existing presets, initializing missing policyPresets, skipping when none applied, handling read failures (logs error + continues), and avoiding duplicates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as "nemoclaw CLI"
  participant Rebuild as "sandboxRebuild"
  participant Policies as "policies.mergePresetsIntoSession"
  participant Registry as "registry (applied presets)"
  participant Session as "onboardSession (sessionMod)"

  CLI->>Rebuild: trigger rebuild
  Rebuild->>Rebuild: backup workspace
  Rebuild->>Policies: mergePresetsIntoSession(sandboxName, sessionMod, log)
  Policies->>Registry: getAppliedPresets(sandboxName)
  Registry-->>Policies: list of applied presets / error
  Policies->>Session: updateSession(...) with merged policyPresets
  Session-->>Policies: updated session state
  Policies-->>Rebuild: return (success or logged warning)
  Rebuild->>Rebuild: continue destroy + onboard + restore
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code with tiny feet,
Merged lost presets so rebuilds are neat.
No more Telegram lost in the night,
Deduped and ordered — everything's right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 accurately summarizes the primary change: preserving manually-added policy presets during sandbox rebuild, which directly addresses the main problem described in the changeset.
Linked Issues check ✅ Passed All coding objectives from #1952 are met: the PR records applied presets via registry before deletion, merges them into session with deduplication, and gracefully handles failures by logging warnings without blocking rebuild.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: adding the mergePresetsIntoSession helper, integrating it into sandboxRebuild, and comprehensive test coverage for the feature. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/1952-rebuild-restore-policy-presets

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/nemoclaw.ts`:
- Around line 1814-1816: The catch block that handles failure to read applied
presets currently calls log(...) which is suppressed in non-verbose runs;
replace that call with a visible warning (e.g. console.warn(...) or the app's
warning-level logger method) so users see the fallback to session-only presets.
Locate the catch handling for "could not read applied presets" (the log(...)
call in src/nemoclaw.ts) and change it to console.warn(`Warning: could not read
applied presets (degraded sandbox?): ${err.message}`) or equivalent
logger.warn(...) to ensure the message is always emitted.

In `@test/rebuild-policy-presets.test.ts`:
- Around line 52-56: Tests are reimplementing the merge/session-update logic
instead of exercising the production code, so update the test to call the real
production path: either invoke sandboxRebuild with mocked side-effects (mock
openShell, backup/restore, etc.) or extract the session-merge helper from
src/nemoclaw.ts into a testable function and call that helper from the test;
replace the inline merge block in rebuild-policy-presets.test.ts with assertions
against the production helper or sandboxRebuild invocation and use mocks/stubs
for external IO to keep the test hermetic.
🪄 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: Pro Plus

Run ID: 91958686-5a00-42b7-bcf0-19a27a5175e1

📥 Commits

Reviewing files that changed from the base of the PR and between 946c52b and a4976dd.

📒 Files selected for processing (2)
  • src/nemoclaw.ts
  • test/rebuild-policy-presets.test.ts

Comment thread src/nemoclaw.ts Outdated
Comment thread test/rebuild-policy-presets.test.ts Outdated
…ning

Address CodeRabbit review feedback on PR #2023:

1. The catch block in the preset-merge step used verbose-only `log()`,
   hiding the fallback warning from normal users. Now emits via
   `console.error()` so it is always visible.

2. Tests reimplemented the merge logic inline instead of exercising
   production code. Extracted `mergePresetsIntoSession()` into
   `src/lib/policies.ts` and rewrote tests to call the production
   helper with real registry data and permission-error simulation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/lib/policies.ts`:
- Around line 488-506: The try/catch around both getAppliedPresets and the
session merge masks failures from sessionMod.updateSession/loadSession; narrow
the scope so only getAppliedPresets(sandboxName) is inside the try and any error
from that logs the current console.error/log message, then perform
sessionMod.updateSession(...) and sessionMod.loadSession() outside that catch
(or in a separate try/catch) so failures in sessionMod.updateSession or
sessionMod.loadSession produce their own error messages and aren’t misreported
as a preset-read failure; update the log statements to reflect which operation
failed and reference getAppliedPresets, sessionMod.updateSession, and
sessionMod.loadSession in the changes.
🪄 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: Pro Plus

Run ID: 623daf31-b183-4f96-8ba7-68865dac64ee

📥 Commits

Reviewing files that changed from the base of the PR and between a4976dd and 43b7265.

📒 Files selected for processing (3)
  • src/lib/policies.ts
  • src/nemoclaw.ts
  • test/rebuild-policy-presets.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/rebuild-policy-presets.test.ts
  • src/nemoclaw.ts

Comment thread src/lib/policies.ts
Comment on lines +488 to +506
try {
const appliedPresets = getAppliedPresets(sandboxName);
log(`Applied presets before rebuild: ${appliedPresets.join(", ") || "(none)"}`);
if (appliedPresets.length > 0) {
sessionMod.updateSession((s) => {
const sessionPresets = Array.isArray(s.policyPresets) ? s.policyPresets : [];
s.policyPresets = [...new Set([...sessionPresets, ...appliedPresets])];
return s;
});
log(`Session policyPresets updated: ${sessionMod.loadSession()?.policyPresets?.join(", ")}`);
}
} catch (err) {
const reason = err && err.message ? err.message : String(err);
console.error(
` Warning: could not read applied presets; continuing rebuild with session presets only (${reason}).`,
);
log(`Applied preset lookup failed: ${reason}`);
// Fall back to whatever the session already has — don't block rebuild.
}
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.

⚠️ Potential issue | 🟠 Major

Narrow the catch scope to avoid masking session-merge failures.

The current try/catch handles both preset-read and session-update paths, but always reports a read failure. If updateSession/loadSession fails, the real failure is masked and merged presets may be silently dropped.

Proposed fix
 function mergePresetsIntoSession(sandboxName, sessionMod, log = () => {}) {
-  try {
-    const appliedPresets = getAppliedPresets(sandboxName);
-    log(`Applied presets before rebuild: ${appliedPresets.join(", ") || "(none)"}`);
-    if (appliedPresets.length > 0) {
-      sessionMod.updateSession((s) => {
-        const sessionPresets = Array.isArray(s.policyPresets) ? s.policyPresets : [];
-        s.policyPresets = [...new Set([...sessionPresets, ...appliedPresets])];
-        return s;
-      });
-      log(`Session policyPresets updated: ${sessionMod.loadSession()?.policyPresets?.join(", ")}`);
-    }
-  } catch (err) {
+  let appliedPresets;
+  try {
+    appliedPresets = getAppliedPresets(sandboxName);
+  } catch (err) {
     const reason = err && err.message ? err.message : String(err);
     console.error(
       `  Warning: could not read applied presets; continuing rebuild with session presets only (${reason}).`,
     );
     log(`Applied preset lookup failed: ${reason}`);
-    // Fall back to whatever the session already has — don't block rebuild.
+    return;
+  }
+
+  log(`Applied presets before rebuild: ${appliedPresets.join(", ") || "(none)"}`);
+  if (appliedPresets.length === 0) return;
+
+  try {
+    sessionMod.updateSession((s) => {
+      const sessionPresets = Array.isArray(s.policyPresets) ? s.policyPresets : [];
+      s.policyPresets = [...new Set([...sessionPresets, ...appliedPresets])];
+      return s;
+    });
+    log(`Session policyPresets updated: ${sessionMod.loadSession()?.policyPresets?.join(", ") || "(none)"}`);
+  } catch (err) {
+    const reason = err && err.message ? err.message : String(err);
+    console.error(
+      `  Warning: could not merge applied presets into session; continuing rebuild with session presets only (${reason}).`,
+    );
+    log(`Session policyPresets merge failed: ${reason}`);
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const appliedPresets = getAppliedPresets(sandboxName);
log(`Applied presets before rebuild: ${appliedPresets.join(", ") || "(none)"}`);
if (appliedPresets.length > 0) {
sessionMod.updateSession((s) => {
const sessionPresets = Array.isArray(s.policyPresets) ? s.policyPresets : [];
s.policyPresets = [...new Set([...sessionPresets, ...appliedPresets])];
return s;
});
log(`Session policyPresets updated: ${sessionMod.loadSession()?.policyPresets?.join(", ")}`);
}
} catch (err) {
const reason = err && err.message ? err.message : String(err);
console.error(
` Warning: could not read applied presets; continuing rebuild with session presets only (${reason}).`,
);
log(`Applied preset lookup failed: ${reason}`);
// Fall back to whatever the session already has — don't block rebuild.
}
function mergePresetsIntoSession(sandboxName, sessionMod, log = () => {}) {
let appliedPresets;
try {
appliedPresets = getAppliedPresets(sandboxName);
} catch (err) {
const reason = err && err.message ? err.message : String(err);
console.error(
` Warning: could not read applied presets; continuing rebuild with session presets only (${reason}).`,
);
log(`Applied preset lookup failed: ${reason}`);
return;
}
log(`Applied presets before rebuild: ${appliedPresets.join(", ") || "(none)"`);
if (appliedPresets.length === 0) return;
try {
sessionMod.updateSession((s) => {
const sessionPresets = Array.isArray(s.policyPresets) ? s.policyPresets : [];
s.policyPresets = [...new Set([...sessionPresets, ...appliedPresets])];
return s;
});
log(`Session policyPresets updated: ${sessionMod.loadSession()?.policyPresets?.join(", ") || "(none)"}`);
} catch (err) {
const reason = err && err.message ? err.message : String(err);
console.error(
` Warning: could not merge applied presets into session; continuing rebuild with session presets only (${reason}).`,
);
log(`Session policyPresets merge failed: ${reason}`);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/policies.ts` around lines 488 - 506, The try/catch around both
getAppliedPresets and the session merge masks failures from
sessionMod.updateSession/loadSession; narrow the scope so only
getAppliedPresets(sandboxName) is inside the try and any error from that logs
the current console.error/log message, then perform
sessionMod.updateSession(...) and sessionMod.loadSession() outside that catch
(or in a separate try/catch) so failures in sessionMod.updateSession or
sessionMod.loadSession produce their own error messages and aren’t misreported
as a preset-read failure; update the log statements to reflect which operation
failed and reference getAppliedPresets, sessionMod.updateSession, and
sessionMod.loadSession in the changes.

@wscurran wscurran added good first issue Good for newcomers NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). fix Integration: Telegram Use this label to identify Telegram bot integration issues with NemoClaw. labels Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix good first issue Good for newcomers Integration: Telegram Use this label to identify Telegram bot integration issues with NemoClaw. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NemoClaw][brev]nemoclaw rebuild does not restore Telegram (messaging) policy presets — bridge starts but cannot reach api.telegram.org

2 participants