Skip to content

feat(runtime): add resilient recovery control plane - #13

Merged
boh5 merged 3 commits into
mainfrom
codex/runtime-control-plane-recovery
Aug 4, 2026
Merged

feat(runtime): add resilient recovery control plane#13
boh5 merged 3 commits into
mainfrom
codex/runtime-control-plane-recovery

Conversation

@boh5

@boh5 boh5 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • keep the HTTP control plane and Settings available when Runtime activation or global Config validation fails
  • add bounded Runtime Data inspection and per-project invalid-data deletion with same-process recovery
  • add selective Config recovery that removes only user-confirmed invalid items after full candidate validation
  • keep whole-Config reset as a strongly warned, typed-confirmation last resort

Why

Previously, an invalid persisted Runtime record or strict Config error could prevent the Runtime-backed server from becoming usable, leaving users without an in-product recovery path. Config recovery was also too destructive when only one bounded item was invalid.

User impact

Users can now open Settings during recovery, inspect affected Runtime data, remove only selected invalid Config items, preserve healthy providers/models/profiles/MCP entries and secrets, and retry activation without restarting the process. Failed or stale recovery attempts leave the original Config unchanged.

Architecture

  • Host owns the always-available control plane and serializes Runtime activation/recovery
  • Agent Core owns Runtime-data inspection and Config recovery transactions
  • Protocol carries only bounded, redacted recovery DTOs
  • Web renders recovery through the existing Settings shell
  • no migration, compatibility fallback, backup/restore subsystem, or upgrade coupling is added

Validation

  • bun run typecheck
  • bun run test
  • bun run build
  • ./dist/archcode --versionarchcode 0.0.8
  • git diff --check origin/main...HEAD
  • real-browser Config and Runtime recovery QA at responsive widths
  • three-round independent AC-08 review; final result had no P0/P1/P2

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added Config Recovery tools for reviewing issues, retrying validation, selectively removing invalid entries, or resetting configuration.
    • Added Runtime controls for status, retry, data inspection, and project-specific data deletion.
    • Added terminal access for setup and configuration recovery.
  • Improvements
    • Settings remain available when Runtime activation fails, with clearer recovery states and notices.
    • Runtime cleanup supports safer retries and reports partial failures.
    • Recovery actions include confirmations, authorization, and safer diagnostic handling.
  • Documentation
    • Added specifications and completion records for recovery workflows.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

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: Pro Plus

Run ID: 8be1062f-e768-4cd4-b685-004caf2cf1bd

📥 Commits

Reviewing files that changed from the base of the PR and between 624b37f and 5d0225d.

📒 Files selected for processing (5)
  • apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.tsx
  • packages/agent-core/src/runtime-data/service.test.ts
  • packages/agent-core/src/runtime-data/service.ts
  • packages/protocol/src/runtime-data.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/protocol/src/runtime-data.ts
  • packages/agent-core/src/runtime-data/service.ts

📝 Walkthrough

Walkthrough

The change keeps the control plane available during Runtime and configuration failures. It adds deferred activation, Config Recovery, Runtime Data inspection and deletion, serialized mutations, new protocol contracts, and Settings recovery workflows.

Changes

Runtime Control Plane Recovery

Layer / File(s) Summary
Protocol and recovery contracts
packages/protocol/src/*, packages/agent-core/src/config/*, packages/agent-core/src/runtime-data/*
Added Runtime status, Config Recovery, and Runtime Data contracts. Added revision-bound configuration removal, sanitized diagnostics, atomic rollback, Runtime Data inspection, safe deletion, and secret redaction.
Server orchestration and routes
apps/server/src/server-host.ts, apps/server/src/routes/*, apps/server/src/boot.ts, apps/server/src/main.ts
Added deferred Runtime activation, serialized recovery mutations, Runtime status reporting, Config Recovery and Runtime Control routes, terminal grants, cleanup retry handling, and injected project/runtime-data services.
Web bootstrap and Settings workflows
apps/web/src/components/bootstrap/*, apps/web/src/components/features/*, apps/web/src/api/*
Added terminal-grant recovery access, Config Recovery and Runtime Data APIs, recovery panels, Runtime failure handling, restricted navigation, unavailable-save notices, and confirmation flows.
Validation, exports, and documentation
apps/server/src/**/*.test.ts, apps/web/src/**/*.test.*, packages/agent-core/src/**/*.test.ts, design-system/pages/settings.md, docs/goals/*
Added route, service, host, architecture, and interaction coverage. Migrated runtime fixtures to injected ProjectRegistry and runtimeStorageHomeDir. Re-exported new contracts and documented recovery behavior and acceptance criteria.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title clearly summarizes the main change: adding a resilient Runtime recovery control plane.
Description check ✅ Passed The description covers the change, motivation, architecture, validation, user impact, and security considerations with relevant test evidence.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/features/SettingsDialog.tsx (1)

47-57: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reset savedWhileRuntimeUnavailable when the snapshot changes.

save clears the flag at Line 82 and the Reload button clears it at Line 132. The snapshot effect does not. Any other caller of onReload — for example SettingsSecurityPanel through onConfigChanged — replaces the snapshot while the "Configuration saved. Retry Runtime to use the saved configuration." notice stays on screen. The notice then refers to a save that is no longer the latest change.

Clear the flag with the other per-snapshot state.

🔧 Proposed fix
   useEffect(() => {
     setDraft(cloneConfig(snapshot.config));
     setErrors({});
     setJsonErrors({});
     setSaveError(undefined);
+    setSavedWhileRuntimeUnavailable(false);
     setRestartRequiredSections(snapshot.restartRequiredSections);
     setJsonResetVersion((current) => current + 1);
   }, [snapshot]);
🤖 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 `@apps/web/src/components/features/SettingsDialog.tsx` around lines 47 - 57,
Update the snapshot-change useEffect in the SettingsDialog component to reset
savedWhileRuntimeUnavailable alongside the other per-snapshot state, ensuring
the stale runtime-unavailable notice is cleared whenever snapshot changes.
🧹 Nitpick comments (12)
packages/agent-core/src/config/server-config-service.ts (1)

325-327: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Overlapping removal paths abort a valid selection.

dedupeRemovalTargets removes only exact duplicate paths. The semantic phase can emit both an ancestor and a descendant target for the same provider, for example ["provider", id] from the options check at Line 690 and ["provider", id, "options", "x"] from validateSecretValuePlacement. If the user selects both items, deleteConfigPath deletes the parent first, then throws InvalidConfigRemovalError for the child because the key no longer exists. The file is restored, so no data is lost, but the user cannot apply the full plan.

Delete shallow paths last, or skip a path whose ancestor was already removed.

♻️ Suggested handling for nested selections
-        for (const item of selected as InvalidConfigRemovalItem[]) {
-          deleteConfigPath(candidate, item.path);
-        }
+        const removals = (selected as InvalidConfigRemovalItem[])
+          .map((item) => item.path)
+          .sort((left, right) => right.length - left.length);
+        const removed: readonly string[][] = [];
+        for (const path of removals) {
+          // Skip a path already removed together with its ancestor.
+          if (removed.some((done) => done.length < path.length
+            && done.every((segment, index) => path[index] === segment))) continue;
+          deleteConfigPath(candidate, path);
+          (removed as string[][]).push([...path]);
+        }
🤖 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 `@packages/agent-core/src/config/server-config-service.ts` around lines 325 -
327, Update the removal loop over selected InvalidConfigRemovalItem entries to
handle overlapping paths safely: when an ancestor path is selected, skip its
descendant targets or order removals so descendants are processed before
ancestors, preventing deleteConfigPath from throwing after a parent is removed.
Preserve exact-duplicate deduplication and ensure valid combined selections
apply successfully.
apps/web/src/components/features/SettingsConfigRecoveryPanel.interaction.tsx (1)

161-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

inputText depends on React internals.

The helper reads _valueTracker and searches for a __reactProps$ key, then calls onChange directly. React 19 does not guarantee either name, so a React upgrade breaks this helper and both tests in this file.

Dispatch a real input event after the native value setter. React's delegated event system then produces the synthetic change event.

♻️ Suggested helper without internal access
 async function inputText(value: string, root: ParentNode): Promise<void> {
   const input = root.querySelector<HTMLInputElement>('input[type="text"]');
   if (!input) throw new Error("Missing confirmation input");
   await act(async () => {
-    const previous = input.value;
     const setter = Object.getOwnPropertyDescriptor(dom.window.HTMLInputElement.prototype, "value")!.set!;
     setter.call(input, value);
-    (input as unknown as { _valueTracker?: { setValue(value: string): void } })._valueTracker?.setValue(previous);
-    const propsKey = Object.keys(input).find((key) => key.startsWith("__reactProps$"));
-    const props = propsKey
-      ? (input as unknown as Record<string, { onChange?: (event: { target: HTMLInputElement }) => void }>)[propsKey]
-      : undefined;
-    if (!props?.onChange) throw new Error("Missing confirmation input change handler");
-    props.onChange({ target: input });
+    input.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
     await new Promise((resolve) => setTimeout(resolve, 0));
   });
 }
🤖 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 `@apps/web/src/components/features/SettingsConfigRecoveryPanel.interaction.tsx`
around lines 161 - 177, Update inputText to stop accessing React internals such
as _valueTracker and __reactProps$ or invoking onChange directly. After setting
the input value with the native HTMLInputElement setter, dispatch a real
bubbling input event so React’s delegated event system produces the synthetic
change event, while preserving the existing act and async behavior.
packages/agent-core/src/config/server-config-service.test.ts (1)

717-741: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for nested removal selections and for an externally repaired Config.

Two uncovered paths remain:

  1. A plan that contains both an ancestor path and a descendant path for the same provider. deleteConfigPath currently rejects the descendant after the ancestor is removed. See the comment on server-config-service.ts Lines 325-327.
  2. removeInvalidConfigItems when the file becomes valid on disk after the plan is built. That reaches the ConfigRecoveryConflictError branch at server-config-service.ts Line 274, which no test exercises. Line 843-880 covers only the changed-but-still-invalid case.
🤖 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 `@packages/agent-core/src/config/server-config-service.test.ts` around lines
717 - 741, Extend server-config-service tests to cover nested removal selections
where a plan includes both an ancestor and descendant path for the same
provider, verifying removeInvalidConfigItems handles the selection without
rejecting the descendant after ancestor removal. Add coverage for externally
repaired configuration by building a plan, making the file valid on disk, then
asserting removeInvalidConfigItems raises ConfigRecoveryConflictError.
packages/agent-core/src/__arch__/automation-boundaries.test.ts (1)

56-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These ordering assertions pass when a needle is missing.

indexOf returns -1 for an absent string, and -1 is less than any real index. If a future refactor renames or removes const runtimeApp = createRuntimeApp(runtime, startServer(host.app, or await ArchCodeServerHost.create, the ordering assertions still pass and the architecture guard stops protecting the boundary. Assert each index is not -1 first.

♻️ Suggested guard
+    const indexOfRequired = (source: string, needle: string): number => {
+      const index = source.indexOf(needle);
+      expect(index).toBeGreaterThanOrEqual(0);
+      return index;
+    };

Then replace each x.indexOf(needle) in these comparisons with indexOfRequired(x, needle).

🤖 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 `@packages/agent-core/src/__arch__/automation-boundaries.test.ts` around lines
56 - 66, Update the ordering assertions in automation-boundaries.test.ts to
validate that each searched string exists before comparing positions. Use the
existing or introduce the suggested indexOfRequired helper for the needles in
the runtimeApp, ArchCodeServerHost.create, and startServer(host.app) checks,
then perform the ordering comparisons with its validated results.
apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx (1)

68-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the cleanup-blocked and inspection-failure states.

All four tests start from recoveryAllowed: true and a successful inspection. Two documented states stay untested: runtime.state === "error" with recoveryAllowed: false, where the panel must clear the selection, close the confirmation, and block deletion; and a failed inspectRuntimeData call, where the panel must show the inspection error. Both are the exact guards that prevent a destructive request in a broken Runtime.

🤖 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 `@apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx`
around lines 68 - 178, Extend the “Settings Runtime Data interactions” suite
with coverage for a runtime error whose recoveryAllowed is false, asserting the
selection and confirmation close and no DELETE request is sent, and for an
inspectRuntimeData failure, asserting the inspection error is displayed and
deletion is blocked. Reuse the existing renderPanel, fetch-mocking, and
request-tracking patterns without changing the current successful-deletion
tests.
packages/agent-core/src/runtime-data/service.ts (1)

344-361: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Runtime Data inspection has no resource bounds. The service is specified to produce a bounded inspection DTO, and MAX_SCHEMA_ISSUES_PER_FILE bounds only schema issues. Two other inputs grow with on-disk state, and inspect() runs for every registered project concurrently.

  • packages/agent-core/src/runtime-data/service.ts#L344-L361: reject a file above a maximum byte size before handle.readFile loads it fully into memory; lstatForInspection already provides stat.size.
  • packages/agent-core/src/runtime-data/service.ts#L255-L297: add a maximum issue count in addIssue and stop collecting entries after that limit, so one corrupted tree cannot grow the response without bound.
🤖 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 `@packages/agent-core/src/runtime-data/service.ts` around lines 344 - 361,
Bound runtime-data inspection in packages/agent-core/src/runtime-data/service.ts
at lines 344-361 by checking the size from lstatForInspection before
handle.readFile and treating oversized files as unreadable; also update addIssue
at lines 255-297 to stop collecting issues once a defined maximum issue count is
reached, while preserving existing issue handling below the limit.
apps/web/src/components/features/SettingsDialog.interaction.tsx (1)

632-702: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the Runtime data delete paths.

The new Runtime Data tests cover inspection, disabled controls, and Runtime retry. No test exercises confirmDelete. Both branches of that flow carry risk:

  • A successful delete must clear the selection, close the dialog, and refresh inspection plus Runtime status.
  • A rejected delete currently leaves the confirmation dialog open while the error renders behind it (see the finding in apps/web/src/components/features/SettingsRuntimeDataPanel.tsx).

Add one test per branch so the dialog-close behaviour is pinned.

🤖 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 `@apps/web/src/components/features/SettingsDialog.interaction.tsx` around lines
632 - 702, Add two tests for confirmDelete in SettingsRuntimeDataPanel: verify a
successful deletion clears selected projects, closes the confirmation dialog,
and refreshes inspection and Runtime status; verify a rejected deletion also
closes the confirmation dialog while displaying the error. Reuse the existing
Runtime Data test setup and mock delete requests to cover both branches.
apps/web/src/components/features/SettingsDialog.tsx (1)

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

IndependentSettingsWorkspace omits invalidProfileCount.

SettingsBody passes invalidProfileCount to SettingsSidebar at Line 119 so the Profiles entry shows the attention indicator. IndependentSettingsWorkspace renders the same sidebar without it, so the indicator disappears while the user views Updates or Runtime Data. No config snapshot is loaded in this workspace, so the count is not available here. Confirm this loss of the indicator is intended.

🤖 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 `@apps/web/src/components/features/SettingsDialog.tsx` around lines 213 - 214,
The SettingsSidebar component rendered in IndependentSettingsWorkspace is
missing the invalidProfileCount prop that SettingsBody passes at line 119 to
display the Profiles attention indicator. Either add invalidProfileCount as a
parameter to the IndependentSettingsWorkspace function signature and pass it to
SettingsSidebar, or explicitly pass a default value (such as 0) to
SettingsSidebar to intentionally suppress the indicator in this workspace
context.
apps/server/src/setup-grant.test.ts (1)

2-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename this test file to terminal-grant.test.ts.

The unit under test is now terminal-grant.ts. The coding guidelines require the test file to be colocated and named <name>.test.ts for the file under test. Rename apps/server/src/setup-grant.test.ts to apps/server/src/terminal-grant.test.ts. Also update the test titles at lines 5 and 16, which still describe setup-only behavior even though the grant now serves Config Recovery.

As per coding guidelines: "测试文件应与被测文件 colocate,命名为 <name>.test.ts".

🤖 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 `@apps/server/src/setup-grant.test.ts` around lines 2 - 4, Rename the test file
associated with TerminalGrant from setup-grant.test.ts to
terminal-grant.test.ts, and update the test titles in the TerminalGrant describe
block to reflect Config Recovery grant behavior rather than setup-only behavior.
Keep the existing test coverage and implementation unchanged.

Source: Coding guidelines

apps/web/src/api/config-recovery.ts (1)

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

One narrow apiFetch body type forces three double casts. Every new recovery helper force-casts a typed protocol request through as unknown as Record<string, unknown> because the body parameter of apiFetch does not accept readonly protocol object types. Widen body in apps/web/src/api/client.ts to a JSON-serializable type, then remove the casts.

  • apps/web/src/api/config-recovery.ts#L33-L33: pass body (a ResetInvalidConfigRequest) without a cast.
  • apps/web/src/api/config-recovery.ts#L51-L51: pass body (a RemoveInvalidConfigItemsRequest) without a cast.
  • apps/web/src/api/runtime-data.ts#L18-L18: pass request (a RuntimeDataDeleteRequest) without a cast.
🤖 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 `@apps/web/src/api/config-recovery.ts` at line 33, Widen the body parameter of
apiFetch in apps/web/src/api/client.ts to accept JSON-serializable readonly
protocol objects, then remove the double casts at
apps/web/src/api/config-recovery.ts:33 and :51 and pass the typed body values
directly; likewise pass request directly at apps/web/src/api/runtime-data.ts:18.
Preserve the existing request behavior.
apps/web/src/api/runtime-data.test.ts (1)

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

The assertion does not prove the claim in the test name.

expect.objectContaining ignores extra properties, so this test passes even if inspectRuntimeData sends a body. Assert the absence directly.

💚 Proposed assertion
     await expect(inspectRuntimeData()).resolves.toEqual({ projects: [] });
     expect(fetch).toHaveBeenCalledWith("/api/runtime-data", expect.objectContaining({
       credentials: "same-origin",
     }));
+    const init = (fetch as unknown as ReturnType<typeof mock>).mock.calls[0]![1] as RequestInit;
+    expect(init.body).toBeUndefined();
+    expect(init.method).toBeUndefined();
🤖 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 `@apps/web/src/api/runtime-data.test.ts` around lines 11 - 18, Update the fetch
assertion in the “inspects Runtime data without a request body” test to
explicitly verify that the request has no body, rather than only matching
credentials with expect.objectContaining. Keep the existing endpoint and
credentials checks, and assert the body’s absence directly.
apps/server/src/server-host.test.ts (1)

1744-1753: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Raise the waitForRuntimeState wait budget.

The helper waits at most 100 iterations of Bun.sleep(1), so roughly 100 ms plus scheduling. The tests at lines 1513-1614 and 1616-1740 use the real RuntimeDataService, a real ProjectRegistry, and real filesystem work before the Runtime reaches error. On a loaded CI machine that budget can expire and throw Runtime did not reach error. The repository guidelines forbid retry-based flaky-test mitigation, so the wait must be generous in the helper itself.

♻️ Proposed deadline-based wait
 async function waitForRuntimeState(
   host: ArchCodeServerHost,
   state: "activating" | "ready" | "error",
 ): Promise<void> {
-  for (let attempt = 0; attempt < 100; attempt += 1) {
+  const deadline = Date.now() + 5_000;
+  while (Date.now() < deadline) {
     if (host.getRuntimeStatus().state === state) return;
     await Bun.sleep(1);
   }
   throw new Error(`Runtime did not reach ${state}`);
 }
🤖 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 `@apps/server/src/server-host.test.ts` around lines 1744 - 1753, Increase the
wait budget in waitForRuntimeState so tests using real runtime, registry, and
filesystem work have sufficient time to reach the requested state on loaded CI
machines. Prefer a generous deadline-based wait over the current fixed
100-iteration retry limit, while preserving the existing state check and timeout
error behavior.

Source: Coding guidelines

🤖 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 `@apps/server/src/server-host.ts`:
- Around line 246-261: Remove the unguarded runtimeDataService.inspect() call
from the all-deleted branch in runHostMutation, while preserving the subsequent
activateRuntimeFromCurrentConfig retry and successful deletion response. Do not
allow post-delete inspection failures to reject the mutation or prevent runtime
activation.

In `@apps/web/src/components/bootstrap/BootstrapGate.test.tsx`:
- Around line 152-157: Update the Config Recovery response fixture in the
request handler checking “/api/config-recovery” to include the required
removableItems field with an empty array, matching the ConfigRecoveryStatus
contract and server response.

In `@apps/web/src/components/features/SettingsConfigRecoveryPanel.tsx`:
- Around line 144-218: The SettingsConfigRecoveryPanel renders no recovery
actions when getConfigRecoveryStatus fails and recovery is undefined. Update the
recovery/error rendering around the recovery conditional to provide an available
retry control in that failure state, reusing the existing retry or
status-loading handler and matching the inline retry behavior of
SettingsRuntimeDataPanel; preserve the current recovery details for successful
loads.

In `@apps/web/src/components/features/SettingsRuntimeDataPanel.tsx`:
- Around line 117-123: Update the deleteRuntimeData catch path to close the
confirmation dialog via setConfirmOpen(false) before the finally block runs,
matching the success path so the actionError alert is visible and
statusHeadingRef focus remains outside only after the modal closes.

In `@packages/agent-core/src/config/server-config-service.ts`:
- Around line 1389-1419: Update restoreClaimedConfig so every cleanup unlink of
claimedPath is best-effort: catch and suppress unlink failures while preserving
the original restore error or return behavior. When handle.writeFile or
handle.sync fails, ensure configDiscardError includes claimedPath in its message
so the retained config can be located. Keep the claimed file available when
restoration fails before cleanup.

In `@packages/agent-core/src/runtime-data/service.test.ts`:
- Around line 425-439: Update the chmod-based failure setup in the delete test
around service.delete to skip the test when the effective UID is 0, or otherwise
use a failure injection that remains effective for root. Preserve the existing
assertions for non-root execution and ensure cleanup still restores the
directory permissions.

---

Outside diff comments:
In `@apps/web/src/components/features/SettingsDialog.tsx`:
- Around line 47-57: Update the snapshot-change useEffect in the SettingsDialog
component to reset savedWhileRuntimeUnavailable alongside the other per-snapshot
state, ensuring the stale runtime-unavailable notice is cleared whenever
snapshot changes.

---

Nitpick comments:
In `@apps/server/src/server-host.test.ts`:
- Around line 1744-1753: Increase the wait budget in waitForRuntimeState so
tests using real runtime, registry, and filesystem work have sufficient time to
reach the requested state on loaded CI machines. Prefer a generous
deadline-based wait over the current fixed 100-iteration retry limit, while
preserving the existing state check and timeout error behavior.

In `@apps/server/src/setup-grant.test.ts`:
- Around line 2-4: Rename the test file associated with TerminalGrant from
setup-grant.test.ts to terminal-grant.test.ts, and update the test titles in the
TerminalGrant describe block to reflect Config Recovery grant behavior rather
than setup-only behavior. Keep the existing test coverage and implementation
unchanged.

In `@apps/web/src/api/config-recovery.ts`:
- Line 33: Widen the body parameter of apiFetch in apps/web/src/api/client.ts to
accept JSON-serializable readonly protocol objects, then remove the double casts
at apps/web/src/api/config-recovery.ts:33 and :51 and pass the typed body values
directly; likewise pass request directly at apps/web/src/api/runtime-data.ts:18.
Preserve the existing request behavior.

In `@apps/web/src/api/runtime-data.test.ts`:
- Around line 11-18: Update the fetch assertion in the “inspects Runtime data
without a request body” test to explicitly verify that the request has no body,
rather than only matching credentials with expect.objectContaining. Keep the
existing endpoint and credentials checks, and assert the body’s absence
directly.

In
`@apps/web/src/components/features/SettingsConfigRecoveryPanel.interaction.tsx`:
- Around line 161-177: Update inputText to stop accessing React internals such
as _valueTracker and __reactProps$ or invoking onChange directly. After setting
the input value with the native HTMLInputElement setter, dispatch a real
bubbling input event so React’s delegated event system produces the synthetic
change event, while preserving the existing act and async behavior.

In `@apps/web/src/components/features/SettingsDialog.interaction.tsx`:
- Around line 632-702: Add two tests for confirmDelete in
SettingsRuntimeDataPanel: verify a successful deletion clears selected projects,
closes the confirmation dialog, and refreshes inspection and Runtime status;
verify a rejected deletion also closes the confirmation dialog while displaying
the error. Reuse the existing Runtime Data test setup and mock delete requests
to cover both branches.

In `@apps/web/src/components/features/SettingsDialog.tsx`:
- Around line 213-214: The SettingsSidebar component rendered in
IndependentSettingsWorkspace is missing the invalidProfileCount prop that
SettingsBody passes at line 119 to display the Profiles attention indicator.
Either add invalidProfileCount as a parameter to the
IndependentSettingsWorkspace function signature and pass it to SettingsSidebar,
or explicitly pass a default value (such as 0) to SettingsSidebar to
intentionally suppress the indicator in this workspace context.

In `@apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx`:
- Around line 68-178: Extend the “Settings Runtime Data interactions” suite with
coverage for a runtime error whose recoveryAllowed is false, asserting the
selection and confirmation close and no DELETE request is sent, and for an
inspectRuntimeData failure, asserting the inspection error is displayed and
deletion is blocked. Reuse the existing renderPanel, fetch-mocking, and
request-tracking patterns without changing the current successful-deletion
tests.

In `@packages/agent-core/src/__arch__/automation-boundaries.test.ts`:
- Around line 56-66: Update the ordering assertions in
automation-boundaries.test.ts to validate that each searched string exists
before comparing positions. Use the existing or introduce the suggested
indexOfRequired helper for the needles in the runtimeApp,
ArchCodeServerHost.create, and startServer(host.app) checks, then perform the
ordering comparisons with its validated results.

In `@packages/agent-core/src/config/server-config-service.test.ts`:
- Around line 717-741: Extend server-config-service tests to cover nested
removal selections where a plan includes both an ancestor and descendant path
for the same provider, verifying removeInvalidConfigItems handles the selection
without rejecting the descendant after ancestor removal. Add coverage for
externally repaired configuration by building a plan, making the file valid on
disk, then asserting removeInvalidConfigItems raises
ConfigRecoveryConflictError.

In `@packages/agent-core/src/config/server-config-service.ts`:
- Around line 325-327: Update the removal loop over selected
InvalidConfigRemovalItem entries to handle overlapping paths safely: when an
ancestor path is selected, skip its descendant targets or order removals so
descendants are processed before ancestors, preventing deleteConfigPath from
throwing after a parent is removed. Preserve exact-duplicate deduplication and
ensure valid combined selections apply successfully.

In `@packages/agent-core/src/runtime-data/service.ts`:
- Around line 344-361: Bound runtime-data inspection in
packages/agent-core/src/runtime-data/service.ts at lines 344-361 by checking the
size from lstatForInspection before handle.readFile and treating oversized files
as unreadable; also update addIssue at lines 255-297 to stop collecting issues
once a defined maximum issue count is reached, while preserving existing issue
handling below the limit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 468e6be4-1bd7-4df8-b6ec-009ae193b484

📥 Commits

Reviewing files that changed from the base of the PR and between f8ab13e and 4cdb71e.

📒 Files selected for processing (55)
  • apps/server/src/app.test.ts
  • apps/server/src/app.ts
  • apps/server/src/boot.ts
  • apps/server/src/errors.ts
  • apps/server/src/main.ts
  • apps/server/src/routes/config-recovery.test.ts
  • apps/server/src/routes/config-recovery.ts
  • apps/server/src/routes/runtime-control.test.ts
  • apps/server/src/routes/runtime-control.ts
  • apps/server/src/server-host.test.ts
  • apps/server/src/server-host.ts
  • apps/server/src/setup-grant.test.ts
  • apps/server/src/terminal-grant.ts
  • apps/web/package.json
  • apps/web/src/api/config-recovery.test.ts
  • apps/web/src/api/config-recovery.ts
  • apps/web/src/api/runtime-data.test.ts
  • apps/web/src/api/runtime-data.ts
  • apps/web/src/api/update.ts
  • apps/web/src/components/bootstrap/BootstrapGate.test.tsx
  • apps/web/src/components/bootstrap/BootstrapGate.tsx
  • apps/web/src/components/features/ConfigRecoverySettings.tsx
  • apps/web/src/components/features/SettingsConfigRecoveryPanel.interaction.tsx
  • apps/web/src/components/features/SettingsConfigRecoveryPanel.tsx
  • apps/web/src/components/features/SettingsDialog.interaction.tsx
  • apps/web/src/components/features/SettingsDialog.test.tsx
  • apps/web/src/components/features/SettingsDialog.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.tsx
  • apps/web/src/components/features/SettingsUpdatesPanel.tsx
  • apps/web/src/components/features/settings-helpers.ts
  • apps/web/src/components/features/settings-panels.tsx
  • apps/web/src/main.tsx
  • design-system/pages/settings.md
  • docs/goals/runtime-control-plane-recovery-plan-goal.md
  • docs/goals/runtime-control-plane-recovery-progress.md
  • packages/agent-core/src/__arch__/automation-boundaries.test.ts
  • packages/agent-core/src/__arch__/runtime-data-boundaries.test.ts
  • packages/agent-core/src/config/index.ts
  • packages/agent-core/src/config/server-config-service.test.ts
  • packages/agent-core/src/config/server-config-service.ts
  • packages/agent-core/src/index.ts
  • packages/agent-core/src/lead-architecture-flows.integration.test.ts
  • packages/agent-core/src/main.test.ts
  • packages/agent-core/src/runtime-automations.integration.test.ts
  • packages/agent-core/src/runtime-automations.test.ts
  • packages/agent-core/src/runtime-data/index.ts
  • packages/agent-core/src/runtime-data/service.test.ts
  • packages/agent-core/src/runtime-data/service.ts
  • packages/agent-core/src/runtime-mcp.test.ts
  • packages/agent-core/src/runtime.ts
  • packages/agent-core/src/tool-output/artifact-lifecycle.test.ts
  • packages/protocol/src/bootstrap.ts
  • packages/protocol/src/index.ts
  • packages/protocol/src/runtime-data.ts
💤 Files with no reviewable changes (2)
  • apps/server/src/app.ts
  • apps/server/src/app.test.ts

Comment thread apps/server/src/server-host.ts
Comment thread apps/web/src/components/bootstrap/BootstrapGate.test.tsx
Comment thread apps/web/src/components/features/SettingsConfigRecoveryPanel.tsx Outdated
Comment thread apps/web/src/components/features/SettingsRuntimeDataPanel.tsx
Comment thread packages/agent-core/src/config/server-config-service.ts
Comment thread packages/agent-core/src/runtime-data/service.test.ts
@boh5

boh5 commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Review triage for head 624b37f:

Fixed:

  • removed the fallible post-delete inspection from Runtime activation
  • added Config diagnostics retry and corrected delete-dialog focus/error behavior
  • made Config restore cleanup best-effort while preserving a recoverable claimed copy
  • normalized overlapping Config removal paths
  • bounded Runtime-data inspection by file size and issue count
  • corrected stale Settings save state, architecture assertions, fixtures, naming, and missing edge-case coverage
  • made the chmod failure test portable when the runner is root

Not changed after validation:

  • retained the React 19/JSDOM event fallback because public-event-only dispatch is not reliable in this existing harness
  • did not add duplicate Runtime-data interaction cases already covered across the focused panel and SettingsDialog suites
  • kept invalidProfileCount out of the config-independent workspace because no Config snapshot exists there by design
  • did not widen the shared apiFetch body type or add repository-wide docstring enforcement; both are unrelated broad refactors

Validation: full test suite, typecheck, production build, binary version check, and diff checks pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
apps/web/src/components/features/SettingsRuntimeDataPanel.tsx (1)

85-88: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Separate Runtime status refresh failures from recovery-operation failures.

If onRefreshRuntime() rejects after retryRuntime() succeeds, the catch treats the refresh as a retry failure and invokes onRefreshRuntime() again. A second rejection can escape the void event handler. If the delete request succeeds but the final refresh rejects, the panel reports “Unable to delete Runtime data.” even though deletion completed.

  • apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L85-L88: Refresh once in a separate error path after retry. Preserve the retry result.
  • apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L116-L122: Report status-refresh failure separately. Do not overwrite a successful deletion result.
🤖 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 `@apps/web/src/components/features/SettingsRuntimeDataPanel.tsx` around lines
85 - 88, Separate recovery-operation errors from status-refresh errors in the
retry and delete handlers. Around the retryRuntime flow at
apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L85-L88, preserve
a successful retry result and perform the single onRefreshRuntime call in its
own guarded error path. Around the delete flow at
apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L116-L122, report
a final refresh failure separately without replacing the successful deletion
result.
packages/agent-core/src/runtime-data/service.ts (1)

263-319: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

为 Runtime Data inspection 增加总预算和有界并发。

MAX_RUNTIME_DATA_ISSUES_PER_PROJECT 只限制返回的 issue 数量。MAX_INSPECTED_JSON_FILE_BYTES 只限制单个 JSON 文件。inspect() 仍使用 Promise.all 检查所有注册项目。scanRuntimeTree() 无界遍历目录项;#inspectSessions() 随后再次枚举 sessions,并为每个 session 读取 JSON 文件。大量项目或深 Runtime tree 会持续消耗控制面的 I/O、CPU 和内存。

  • scanRuntimeTree() 增加目录项、累计字节数、深度和时间预算。
  • 复用 #inspectSessions() 的 session enumeration,或避免第二次遍历。
  • 在继续解析已知 JSON 文件前传播截断状态。
  • 使用有界项目并发,替代 Promise.all
  • 如果新增截断状态,同步 packages/protocol/src/runtime-data.ts、Web UI mapping,并增加大目录树、多 session 和多项目测试。测试必须断言达到预算后停止工作,而不只是断言 issue 数量不超过 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 `@packages/agent-core/src/runtime-data/service.ts` around lines 263 - 319, Add
budget enforcement to prevent unbounded resource consumption during Runtime Data
inspection. In scanRuntimeTree(), add counters and limits for directory items
count, cumulative bytes, depth, and elapsed time, returning early when any
budget is exceeded. In the RuntimeDataStats type (currently tracking fileCount
and totalBytes), add a truncated flag to signal when limits were reached.
Propagate this truncation status through inspectSessions() to avoid parsing JSON
files after budgets are exhausted, and update the flow to reuse the session
enumeration from scanRuntimeTree() rather than enumerating sessions twice. In
the inspect() method, replace Promise.all with bounded concurrency control when
iterating projects instead of launching all inspection tasks simultaneously.
Define budget constants (for directory items, cumulative bytes, depth, and time
limits) alongside existing MAX_RUNTIME_DATA_ISSUES_PER_PROJECT and
MAX_INSPECTED_JSON_FILE_BYTES. Sync the truncation status field to
packages/protocol/src/runtime-data.ts and Web UI mappings, and add tests
verifying that work stops after reaching budgets (not just that issue counts
stay under 100) for scenarios with large directory trees, multiple sessions, and
multiple projects.
🤖 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 `@packages/agent-core/src/runtime-data/service.ts`:
- Around line 352-355: 在 packages/agent-core/src/runtime-data/service.ts 的
inspectJsonFile() 及其预检查、删除前复查路径中,将超出 64 MiB 的问题从 unreadable 独立为
inspection_limit;更新 packages/protocol/src/runtime-data.ts 和 UI 的 issueReason()
以支持该原因。仅有 inspection_limit 时仍须执行 assertSafeDeletionTarget() 并允许删除;补充测试验证超过 64
MiB 的 JSON 文件可被删除。涉及
service.ts:352-355、service.ts:116-123、service.ts:130-132,三个位置均需按上述逻辑更新。

---

Outside diff comments:
In `@apps/web/src/components/features/SettingsRuntimeDataPanel.tsx`:
- Around line 85-88: Separate recovery-operation errors from status-refresh
errors in the retry and delete handlers. Around the retryRuntime flow at
apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L85-L88, preserve
a successful retry result and perform the single onRefreshRuntime call in its
own guarded error path. Around the delete flow at
apps/web/src/components/features/SettingsRuntimeDataPanel.tsx#L116-L122, report
a final refresh failure separately without replacing the successful deletion
result.

In `@packages/agent-core/src/runtime-data/service.ts`:
- Around line 263-319: Add budget enforcement to prevent unbounded resource
consumption during Runtime Data inspection. In scanRuntimeTree(), add counters
and limits for directory items count, cumulative bytes, depth, and elapsed time,
returning early when any budget is exceeded. In the RuntimeDataStats type
(currently tracking fileCount and totalBytes), add a truncated flag to signal
when limits were reached. Propagate this truncation status through
inspectSessions() to avoid parsing JSON files after budgets are exhausted, and
update the flow to reuse the session enumeration from scanRuntimeTree() rather
than enumerating sessions twice. In the inspect() method, replace Promise.all
with bounded concurrency control when iterating projects instead of launching
all inspection tasks simultaneously. Define budget constants (for directory
items, cumulative bytes, depth, and time limits) alongside existing
MAX_RUNTIME_DATA_ISSUES_PER_PROJECT and MAX_INSPECTED_JSON_FILE_BYTES. Sync the
truncation status field to packages/protocol/src/runtime-data.ts and Web UI
mappings, and add tests verifying that work stops after reaching budgets (not
just that issue counts stay under 100) for scenarios with large directory trees,
multiple sessions, and multiple projects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f446d93e-8628-4b23-ab93-bf8c419bad4a

📥 Commits

Reviewing files that changed from the base of the PR and between 4cdb71e and 624b37f.

📒 Files selected for processing (15)
  • apps/server/src/server-host.test.ts
  • apps/server/src/server-host.ts
  • apps/server/src/terminal-grant.test.ts
  • apps/web/src/components/bootstrap/BootstrapGate.test.tsx
  • apps/web/src/components/features/SettingsConfigRecoveryPanel.interaction.tsx
  • apps/web/src/components/features/SettingsConfigRecoveryPanel.tsx
  • apps/web/src/components/features/SettingsDialog.interaction.tsx
  • apps/web/src/components/features/SettingsDialog.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.tsx
  • packages/agent-core/src/__arch__/automation-boundaries.test.ts
  • packages/agent-core/src/config/server-config-service.test.ts
  • packages/agent-core/src/config/server-config-service.ts
  • packages/agent-core/src/runtime-data/service.test.ts
  • packages/agent-core/src/runtime-data/service.ts
💤 Files with no reviewable changes (1)
  • apps/server/src/server-host.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/agent-core/src/arch/automation-boundaries.test.ts
  • apps/web/src/components/bootstrap/BootstrapGate.test.tsx
  • apps/web/src/components/features/SettingsRuntimeDataPanel.interaction.tsx
  • packages/agent-core/src/runtime-data/service.test.ts
  • apps/web/src/components/features/SettingsDialog.tsx
  • packages/agent-core/src/config/server-config-service.ts
  • apps/server/src/server-host.test.ts

Comment thread packages/agent-core/src/runtime-data/service.ts
@boh5

boh5 commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Second review triage for head 5d0225d:

Fixed:

  • oversized JSON now reports inspection_limit instead of unreadable, so the UI warns that it was not parsed while safe whole-tree deletion remains available after the existing structural and symlink checks
  • Runtime retry/delete results are now separate from the later Bootstrap refresh result; a refresh failure is shown independently, never changes a successful operation into a reported failure, and refresh runs once
  • added focused deletion and UI interaction coverage; full tests, typecheck, production build, binary version, and diff checks pass

Not folded into this PR:

  • the proposed full scanner-budget redesign (time/depth/aggregate-byte budgets, protocol truncation state, traversal reuse, and concurrency framework) is a separate performance architecture change, not a correctness fix required for this recovery path
  • deletion must still traverse the complete tree before recursive removal to fail closed on descendant symlinks; truncating that safety walk would weaken the deletion boundary
  • the current inspection response and per-file JSON parsing are bounded; broader filesystem traversal budgeting should be designed independently with explicit product semantics for partial inspection

@boh5
boh5 merged commit f00efe7 into main Aug 4, 2026
7 checks passed
@boh5
boh5 deleted the codex/runtime-control-plane-recovery branch August 4, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant