Skip to content

Prevent hibernation from reaping live agent processes - #6576

Merged
austinywang merged 76 commits into
mainfrom
issue-6565-hibernation-transcript-loss
Jul 10, 2026
Merged

austinywang merged 76 commits into
mainfrom
issue-6565-hibernation-transcript-loss

Conversation

@austinywang

@austinywang austinywang commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • prevent hibernation from selecting or confirming panes with live scoped agent processes while still counting those panes toward the live cap
  • snapshot Claude transcripts off-main before teardown and restore metadata-only clobbers through bounded post-teardown checks that survive normal focus/resume activity
  • fail closed when ambiguous standard, any-project direct/workflow, workflow, or panel-scoped hook Claude transcript candidates find multiple populated files; scan past workflow metadata stubs; and cancel stale restore monitors only on replacement/global teardown paths
  • scope recorded Claude hook transcript paths to the hibernating workspace/panel so stale same-session records from other panes cannot be snapshotted or restored
  • validate copied teardown snapshots before trusting them, retain cancelled restore snapshots for manual recovery, clean completed restore snapshots, cancel restore monitors from the shared tracking clear path, and order post-snapshot validation scans after each pane snapshot
  • reuse the in-flight post-snapshot validation scan until completion so bulk hibernation does not fan out duplicate expensive index loads
  • split the new hibernation helper types into one-type Swift files and replace runtime Task.sleep calls with bounded ContinuousClock sleeps

Testing

  • git diff --check
  • python3 scripts/normalize-pbxproj.py --check
  • /Users/austinwang/manaflow/cmuxterm-hq/skills/review/autoreview/scripts/cmux-policy-check --mode local --base origin/main
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv --base-ref "$(git merge-base origin/main HEAD)"
  • xcodebuild -quiet -project cmux.xcodeproj -scheme cmux-unit -configuration Debug -destination 'platform=macOS' -derivedDataPath /Users/austinwang/Library/Developer/Xcode/DerivedData/cmux-issue-6565-hibernation-transcript-loss -only-testing:cmuxTests/AgentHibernationPlannerSwiftTests -only-testing:cmuxTests/AgentHibernationTranscriptGuardTests -only-testing:cmuxTests/AgentHibernationTranscriptGuardScanTests test
  • ./scripts/reload.sh --tag issue-6565-hibernation-transcript-loss
  • /Users/austinwang/manaflow/cmuxterm-hq/skills/review/autoreview/scripts/autoreview --mode branch --base origin/main
  • python3 /Users/austinwang/manaflow/cmuxterm-hq/skills/autoreview/scripts/wait_pr_status.py 6576 --repo manaflow-ai/cmux -> 30 pass, 0 pending, 0 fail
  • PR feedback audit: all 17 inline review threads resolved; no unresolved review threads

Demo Video

  • Not included; this is background hibernation/transcript-protection behavior covered by focused unit tests and CI.

Review Trigger

  • $autoreview requested. Latest Cursor Bugbot feedback addressed in dd559f8099; canonical autoreview follow-ups addressed in 28687f0a84, 78429d600d, e91f95cf51, 0cfb02648d, f88ef5d09b, 3ac25b25f3, e369595d8f, 0d07d8c1ec, 9f9c8c0210, 700f1c13da, a7ff6c834d, 7a76137fe3, 7694951ea9, d503d3b996, 6b404a17e7, b89b39184a, 8e842b30c5, fdb36ec4f3, and 776ad6c3f1.

Checklist

  • Regression coverage added for live-process hibernation eligibility and transcript guard restore/cancel behavior
  • Cursor Bugbot threads replied to and resolved
  • Canonical autoreview findings addressed
  • GitHub CI/CD green on latest head

Closes #6565


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Prevents hibernation from tearing down panes with live scoped agent processes and adds a fail-closed Claude transcript guard that snapshots before teardown and safely restores clobbered .jsonl files. Hardens restore monitors with a protection handoff and a bounded recovery slot to close remaining races. Closes #6565.

  • Bug Fixes

    • Live-process panes: skip selection, confirmation, and tail sampling; still count toward the cap; clear pending samples.
    • Claude transcripts: snapshot off-main before SIGTERM/pty-close; honor hook-recorded transcriptPath first; scope hook records to the panel; search all config roots; prefer populated candidates; validate session IDs/paths; bound scans; skip workflow stubs; fail closed or mark temporary unable-to-protect.
    • Restore: run immediate and backstopped checks keyed to process exit; reconfirm state before restore; stream via clone with atomic promote; make monitor replacement a protection handoff; retain unrestored snapshots in a bounded per-session recovery slot; drain-and-wait on bulk cancellations; de-duplicate retained copies; resolve symlinked keys; loop-fill snapshot comparisons and record a file-version triple without suspension.
  • Refactors

    • Extract records/teardown/planner into dedicated files and add AgentHibernationTranscriptGuard. Migrate tests to Swift Testing with coverage for resolver priority, scoped hook transcripts, ambiguous/unsafe transcripts, oversized-line scan, streaming restore, live-process reaping, PID-epoch invalidation, monitor handoff/recovery, bulk-cancel drain, symlink aliasing, and snapshot race handling.
    • Split sidebar status visibility helpers into Workspace+SidebarStatusVisibility.swift to keep Workspace+PanelLifecycle.swift within the file-length budget.
    • Tests: stop the planner suite from bulk-cancelling shared restore monitors; clean up only the suite’s own entry by request ID to avoid cross-suite flakiness.

Written for commit 2869915. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved hibernation eligibility by separating “live process” from “live terminal surface,” preventing live-scoped processes from being selected for teardown/hibernation.
    • Updated confirmation rules to require a live terminal surface while explicitly rejecting live processes; cleared related confirmations when a live process is detected.
    • Tightened tail-fingerprint handling so activity sampling and fingerprint fallback no longer apply to live processes.
  • Tests
    • Expanded planner test cases for live-process inputs.
    • Added a regression test ensuring idle agents with live-scoped running processes are not reaped.

Note

High Risk
Changes agent hibernation teardown, SIGTERM timing, and on-disk Claude transcript files with complex async races; mistakes could drop conversations or leave orphaned processes.

Overview
Hibernation no longer tears down panes with live scoped agent processes—they still count toward the live cap but are excluded from planner selection, confirmation, and tail fingerprint sampling. PID changes bump a per-panel teardown epoch so in-flight teardowns abort when the runtime changes.

Confirmed teardown is now async and transcript-safe: Claude .jsonl files are snapshotted off the main actor before SIGTERM/pty-close, with re-validation (fresh session index, fingerprints, protection, live-process checks) before commit. Ambiguous or unsafe transcript resolution fails closed with a temporary unable-to-protect backoff instead of risking conversation loss (#6565).

A new AgentHibernationTranscriptGuard resolves panel-scoped hook paths, copies and byte-validates snapshots, restores metadata-only clobbers via bounded post-teardown monitors (symlink-deduped, handoff on replacement, drain on bulk cancel), and retains unrestored copies in a per-session recovery slot. Controller logic is split across extension files; workspace PID hooks notify hibernation on process attach/detach.

Reviewed by Cursor Bugbot for commit 2869915. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 10, 2026 12:15pm
cmux-staging Building Building Preview, Comment Jul 10, 2026 12:15pm

@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a hasLiveProcess boolean field to AgentHibernationPlannerInput and propagates it through the hibernation pipeline: the planner excludes live-process records from its reaping candidate pool, the controller skips tail-fingerprint sampling and clears state for them, and evaluateConfirmation now requires !hasLiveProcess && hasLiveSurface instead of the previous hasLiveSurface || hasLiveProcess. Fingerprint computation fallback to process-based fingerprints is removed. Tests are updated with explicit hasLiveProcess flags and a new regression test is added to verify idle agents with running processes are not selected.

Changes

Agent Hibernation — Live Process Guard

Layer / File(s) Summary
Input contract and planner eligibility
Sources/App/AgentHibernationController.swift
Adds hasLiveProcess: Bool to AgentHibernationPlannerInput (line 14) and extends selectedPanelKeys filtering to exclude inputs where hasLiveProcess == true from the live-restorable candidate pool (lines 32–37).
Controller state and tail-sampling management
Sources/App/AgentHibernationController.swift
Computes shouldMaintainTailSamples based only on records that are live and !hasLiveProcess (lines 208–215). Clears tailFingerprintSamples and confirmations immediately when record.hasLiveProcess is true and prevents tail-fingerprint updates via updateTailFingerprintSample unless !hasLiveProcess.
Planner input wiring and confirmation guard strengthening
Sources/App/AgentHibernationController.swift
Forwards record.hasLiveProcess into the AgentHibernationPlannerInput initializer during evaluate (line 226). Rewrites evaluateConfirmation guard from (hasLiveSurface || hasLiveProcess) to (hasLiveSurface && !hasLiveProcess) (lines 260–261).
Fingerprint logic simplification
Sources/App/AgentHibernationController.swift
Removes the process-based fingerprint fallback path from hibernationFingerprint(for:) (lines 330–337). Now returns only the scrollback-derived fingerprint when tail text is present, otherwise returns nil.
Regression tests and validation
cmuxTests/AgentHibernationTests.swift
Introduces liveProcessOld panel key variable (line 137) and reformats test inputs with explicit hasLiveProcess values in testPlannerOnlySelectsIdleUnprotectedExcessLiveAgents (lines 149–155). Adds hasLiveProcess: false to testPlannerDoesNotSelectWhenUnderLiveLimit (line 175). Adds testPlannerDoesNotReapIdleAgentWithLiveScopedProcess regression test verifying that an idle panel with an active running process is not selected for reaping (lines 164–187).

Sequence Diagram(s)

sequenceDiagram
    participant Record
    participant Controller
    participant TailSamples
    participant Confirmation
    participant Planner

    Record->>Controller: evaluate(record, isLiveByKey)
    Note over Controller: Check record.hasLiveProcess
    alt hasLiveProcess == true
        Controller->>TailSamples: clear()
        Controller->>Confirmation: clear()
        Controller->>Planner: Input with hasLiveProcess=true
        Planner->>Planner: Exclude from selection
    else hasLiveProcess == false
        Controller->>TailSamples: updateTailFingerprintSample()
        Controller->>Planner: Input with hasLiveProcess=false
        Planner->>Planner: Eligible for selection
        Note over Planner: Check if excess & unprotected
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#4165: Originally introduced the Agent Hibernation planner/controller infrastructure in AgentHibernationController.swift — the exact files and control flow this PR modifies to add hasLiveProcess gating.
  • manaflow-ai/cmux#5433: Improves live-scoped process detection in RestorableAgentSession (e.g., node-backed Claude agents), which directly determines the accuracy of the hasLiveProcess value that this PR now uses to gate hibernation eligibility and confirmation.

Poem

🐇 Hop, skip—a process lives on!
Don't reap while the agent runs strong.
hasLiveProcess blocks the gate,
No transcript clobbered, no data-loss fate.
Guard the running, preserve the chat—
A careful bunny keeps it intact! 🌿


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Source Artifacts ❌ Error .claude/scheduled_tasks.lock contains local tool output (runtime state: PID, timestamps, session IDs) added to source control without deliberate product reason, violating source-control-artifacts... Remove .claude/scheduled_tasks.lock from the diff or add **/.claude/scheduled_tasks.lock to .gitignore to exclude local runtime lock files.
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 (21 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully addresses the objectives in #6565 by adding regression tests for live-process scenarios and excluding live scoped processes from hibernation selection, confirmation, and tail sampling.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the stated objectives: regression test addition, live-process filtering in hibernation selection/confirmation, and fingerprint handling modifications are all in scope for preventing hibernation of active agents.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation violations detected. All value models properly isolated with Sendable/MainActor, helper functions marked nonisolated, and UI-bound structures correctly isolated.
Cmux Swift Blocking Runtime ✅ Passed Pull request introduces only logic filters and data field additions with no blocking/timing-based synchronization primitives. Existing DispatchSourceTimer is unchanged.
Cmux Expensive Synchronous Load ✅ Passed The PR does not add expensive synchronous agent-history loads to main actor or interactive paths. The RestorableAgentSessionIndex.loadIncludingProcessDetectedSnapshots() call remains properly off-m...
Cmux Cache Substitution Correctness ✅ Passed The fingerprint is stored in transient in-memory cache (not persisted), and changes only remove a fallback path when tail text is unavailable, not swap fresh reads for cached values. Early-return o...
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift files (.swift), which are explicitly out of scope for runtime-no-hacky-sleeps.md—Swift timing is covered by a separate rule (swift-blocking-runtime.md).
Cmux Algorithmic Complexity ✅ Passed PR adds hasLiveProcess filtering to hibernation logic without algorithmic violations. All operations are linear passes or acceptable O(N log N) on 30-second timer; no nested scans, per-target resca...
Cmux Swift Concurrency ✅ Passed The PR does not introduce or materially expand legacy async patterns. Pre-existing DispatchQueue for timer management and Task patterns for AppKit/NotificationCenter boundaries remain unchanged. Al...
Cmux Swift @Concurrent ✅ Passed No async functions or @concurrent annotations are introduced/modified in this PR; changes are purely business logic (hasLiveProcess field additions and condition updates). Existing async patterns w...
Cmux Swift File And Package Boundaries ✅ Passed Focused bug fix updating existing files under size thresholds with coherent hibernation logic responsibility; no new oversized files, no 250+ line additions to 800+ line files, no mixed responsibil...
Cmux Swiftpm Lockfiles ✅ Passed PR modifies only Swift source/test files (AgentHibernationController.swift, AgentHibernationTests.swift), not SwiftPM packages, Xcode projects, .gitignore, workflows, or dependencies—the check appl...
Cmux Swift Logging ✅ Passed Production Swift code (AgentHibernationController.swift) contains zero print/debugPrint/dump/NSLog calls, no file-scoped Logger constants, and no ad hoc file/stdout logging. Test code contains no l...
Cmux User-Facing Error Privacy ✅ Passed Changes are internal Swift implementation and tests with no user-facing errors, sensitive information, or vendor details exposed. All string literals are internal (queue label, fingerprint prefix,...
Cmux Full Internationalization ✅ Passed No user-facing strings were added or modified. Changes are purely logic improvements to hibernation selection and test coverage, with only internal comments, dispatch labels, and fingerprint string...
Cmux Swiftui State Layout ✅ Passed The two changed files (AgentHibernationController.swift and AgentHibernationTests.swift) contain no SwiftUI state patterns or SwiftUI imports; changes are pure business logic for agent hibernation...
Cmux Architecture Rethink ✅ Passed The PR adds a correctness fix with clear ownership and invariants: panes with live processes are excluded from hibernation via consistent filtering (planner, state management, confirmation guards)....
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains only agent hibernation logic and test changes; no new user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup is added or materially changed.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No test or debug seams found in production source. Changes to AgentHibernationController.swift are legitimate production logic: new hasLiveProcess filtering, modified hibernation logic, and extract...
Title check ✅ Passed The title accurately summarizes the main change: excluding live agent processes from hibernation teardown.
Description check ✅ Passed All required sections are present and filled, including Summary, Testing, Demo Video, Review Trigger, and Checklist.
✨ 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 issue-6565-hibernation-transcript-loss

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a live-agent-reaping bug (#6565) by preventing hibernation from selecting or confirming panes with live scoped processes (while still counting them toward the cap), and by adding AgentHibernationTranscriptGuard — a two-phase transcript protection layer that snapshots Claude .jsonl files off-main before SIGTERM and restores metadata-only clobbers through bounded post-teardown monitors.

  • Live-process exclusion: AgentHibernationPlannerInput gains hasLiveProcess; the planner filters those panes from eligible candidates; evaluate bumps the teardown-validation epoch on every live-process observation so any in-flight teardown also aborts.
  • Two-phase teardown (AgentHibernationController+Teardown.swift): snapshots run in a bounded withTaskGroup off-main → cancelled monitors drain → snapshot revalidation runs off-main → a fresh session index loads off-main → per-request guard checks (epoch, generation, fingerprint, file-version) run synchronously before SIGTERM with no suspension point between the file-version check and the kill signal.
  • Transcript guard (AgentHibernationTranscriptGuard): hook-recorded path takes priority, falls back through standard/workflow/any-project candidates with fail-closed ambiguity rules; restore monitors use ContinuousClock.sleep (bounded 30 s process-exit wait + escalating backstop); monitor replacement is a drain-and-handoff, not destroy-then-abort; unrestored snapshots move to a per-session recovery slot.

Confidence Score: 5/5

The change is safe to merge; the two-phase teardown correctly sequences all file I/O off the main actor and the guard chain closes the races that caused #6565.

The critical ordering invariant — live file version checked synchronously with no suspension before SIGTERM — is correctly implemented. Live-process panes are excluded from selection at the planner level and re-checked in the post-snapshot revalidation guard, preventing the reaping regression. All JSONL/transcript I/O runs in bounded utility task groups off the main actor.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/AgentHibernationTranscriptGuard.swift New transcript guard with snapshot-before-teardown, restore-if-clobbered, and recovery-slot logic; all file I/O runs off-main; caseless enum static-namespace shape flags cmux-no-ambient-global-state
Sources/App/AgentHibernationController+Teardown.swift New two-phase teardown: snapshot off-main, drain cancelled monitors, revalidate via fresh index, check file version synchronously before SIGTERM — ordering is correct and no main-actor I/O violations found
Sources/App/AgentHibernationController.swift Planner, records, teardown, and type stubs split into extension files; live-process checks added to evaluate/evaluateConfirmation; epoch bumping on hasLiveProcess correct
Sources/App/AgentHibernationTranscriptGuard+PostTeardownRestore.swift Restore loop: bounded 30 s process-exit wait, initial retry delays, long-haul backstop; uses ContinuousClock.sleep (not Task.sleep), all sleeps cancellation-aware
Sources/App/AgentHibernationTranscriptGuard+ClaudeWorkflow.swift Hook-recorded path first, standard candidates with requireUniqueConversation, workflow recursive scan (depth-4, bounded at 2 protective matches); full directory scan runs off-main in task group
Sources/App/AgentHibernationTranscriptGuard+StableFileComparison.swift readFullChunk loop handles short reads; pre/post version bracketing detects concurrent writes; matchingLiveFileVersion logic is sound

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Timer as DispatchTimer (utility)
    participant Index as RestorableAgentSessionIndex
    participant Controller as AgentHibernationController (MainActor)
    participant Planner as AgentHibernationPlanner
    participant SnapshotTask as Task.detached (utility)
    participant Guard as AgentHibernationTranscriptGuard
    participant RestoreTask as PostTeardownRestoreTask (utility)

    Timer->>Index: loadIncludingProcessDetectedSnapshots()
    Index-->>Controller: index (with hasLiveProcess data)
    Controller->>Planner: selectedPanelKeys(inputs, settings, now)
    Note over Planner: Excludes hasLiveProcess panes, counts toward cap
    Planner-->>Controller: selectedKeys (no live-process panes)
    Controller->>Controller: evaluateConfirmation
    Controller->>Controller: beginConfirmedTeardowns()
    Controller->>SnapshotTask: snapshotOutcomes() off-main
    SnapshotTask->>Guard: snapshotBeforeTeardown
    Guard-->>SnapshotTask: TeardownSnapshotOutcome
    SnapshotTask-->>Controller: outcomes
    Controller->>Controller: drainCancelledMonitors()
    Controller->>SnapshotTask: revalidatedSnapshotOutcomes() off-main
    SnapshotTask-->>Controller: revalidation
    Controller->>Index: load postSnapshot index off-main
    loop for each confirmed request
        Controller->>Controller: guard epoch/fingerprint/liveProcess
        Controller->>Controller: cancelRestoreTask await
        Controller->>Guard: liveFileVersionStillMatches sync
        Controller->>Controller: SIGTERM + enterHibernation
        Controller->>RestoreTask: armPostTeardownRestoreMonitor
        RestoreTask->>Guard: runPostTeardownRestoreChecks
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Timer as DispatchTimer (utility)
    participant Index as RestorableAgentSessionIndex
    participant Controller as AgentHibernationController (MainActor)
    participant Planner as AgentHibernationPlanner
    participant SnapshotTask as Task.detached (utility)
    participant Guard as AgentHibernationTranscriptGuard
    participant RestoreTask as PostTeardownRestoreTask (utility)

    Timer->>Index: loadIncludingProcessDetectedSnapshots()
    Index-->>Controller: index (with hasLiveProcess data)
    Controller->>Planner: selectedPanelKeys(inputs, settings, now)
    Note over Planner: Excludes hasLiveProcess panes, counts toward cap
    Planner-->>Controller: selectedKeys (no live-process panes)
    Controller->>Controller: evaluateConfirmation
    Controller->>Controller: beginConfirmedTeardowns()
    Controller->>SnapshotTask: snapshotOutcomes() off-main
    SnapshotTask->>Guard: snapshotBeforeTeardown
    Guard-->>SnapshotTask: TeardownSnapshotOutcome
    SnapshotTask-->>Controller: outcomes
    Controller->>Controller: drainCancelledMonitors()
    Controller->>SnapshotTask: revalidatedSnapshotOutcomes() off-main
    SnapshotTask-->>Controller: revalidation
    Controller->>Index: load postSnapshot index off-main
    loop for each confirmed request
        Controller->>Controller: guard epoch/fingerprint/liveProcess
        Controller->>Controller: cancelRestoreTask await
        Controller->>Guard: liveFileVersionStillMatches sync
        Controller->>Controller: SIGTERM + enterHibernation
        Controller->>RestoreTask: armPostTeardownRestoreMonitor
        RestoreTask->>Guard: runPostTeardownRestoreChecks
    end
Loading

Reviews (57): Last reviewed commit: "Stop planner suite bulk-cancelling share..." | Re-trigger Greptile

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

Caution

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

⚠️ Outside diff range comments (1)
Sources/App/AgentHibernationController.swift (1)

28-35: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Redundant !input.hasLiveProcess check on line 35.

Since liveRestorable (line 28) already excludes entries where hasLiveProcess == true, the second check at line 35 is redundant. The redundancy is harmless and provides defense-in-depth against future refactoring, so this is acceptable to keep.

🤖 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 `@Sources/App/AgentHibernationController.swift` around lines 28 - 35, The
filter on the eligible constant contains a redundant check for hasLiveProcess.
Since liveRestorable is already filtered on line 28 to exclude any inputs where
hasLiveProcess is true, the condition !input.hasLiveProcess on line 35 is
unnecessary. Remove this redundant condition from the filter closure, keeping
only the !input.isProtected check and any other remaining filter conditions.
🤖 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 `@Sources/App/AgentHibernationController.swift`:
- Around line 28-35: The filter on the eligible constant contains a redundant
check for hasLiveProcess. Since liveRestorable is already filtered on line 28 to
exclude any inputs where hasLiveProcess is true, the condition
!input.hasLiveProcess on line 35 is unnecessary. Remove this redundant condition
from the filter closure, keeping only the !input.isProtected check and any other
remaining filter conditions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88a17d52-d084-407b-8d48-98138f1df279

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8efae and f429596.

📒 Files selected for processing (1)
  • Sources/App/AgentHibernationController.swift

@matthewrball

Copy link
Copy Markdown

Thanks for the fast turnaround, @austinywang — gating both the LRU and idle paths on !hasLiveProcess looks like the right shape.

Flagging two items from the original "Expected behavior" for tracking (not blocking this PR):

  1. The fix now hinges entirely on hasLiveProcess being reliably true throughout a silent-but-busy run. The struct defaults it to false, and the planner excludes live-process panes correctly — but the failure in the report was a multi-agent / Workflow run whose in-process teammates emit no terminal output and may not hold a live process handle on the pane. If there's any window where a busy agent isn't flagged live (or the caller doesn't set it), both the idle (now - lastActivityAt >= idleSeconds) and LRU paths can still select it. Might be worth confirming where hasLiveProcess is sourced and that it stays true across those silent windows.

  2. No last-resort transcript guard. This is a selection/eligibility fix only — it adds no protection to the transcript write itself. If a teardown ever does reach a running agent through any path, the metadata-stub overwrite still destroys the .jsonl. A cheap belt-and-suspenders, independent of selection: never overwrite a populated transcript with a metadata-only stub (snapshot, or treat it as append-only on force-stop). That would make the data loss impossible regardless of how the selection logic evolves.

Happy to help with a follow-up for either once this lands.

Comment thread Sources/App/AgentHibernationController.swift
Comment thread Sources/App/AgentHibernationController.swift

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fdb36ec. Configure here.

Comment thread Sources/App/AgentHibernationController.swift Outdated
austinywang and others added 4 commits July 9, 2026 22:22
Close the transcript-loss races around post-teardown restore monitors:

- Registered monitors stay armed until a teardown commits; every quiesce
  is followed synchronously by a replacement monitor or a forfeit-armed
  monitor on the fresh snapshot, so a stubbed transcript is never left
  with zero monitors.
- Forfeit-armed monitors use a retain-for-recovery disposal: an
  unrestored snapshot whose live path diverged moves into a bounded
  per-session recovery slot instead of being deleted or orphaned.
- Bulk monitor cancellation chains a drain task that teardown batches
  await, so an unregistered cancelled monitor's final restore cannot
  race a batch.
- Monitor registry keys resolve symlinks so aliased transcript paths
  cannot arm two monitors on one file.
- Snapshot byte comparison loop-fills short reads and records a
  file-version triple revalidated with no suspension before SIGTERM.
- Failed snapshot attempts replace a single retained recovery copy per
  session instead of accumulating full-transcript copies.

Regression coverage: monitor replacement/handoff, forfeit-arm restore,
bulk-cancel drain, symlink key aliasing, retained-slot dedupe, and
post-copy/post-comparison snapshot races.

Fixes #6565

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n-transcript-loss

# Conflicts:
#	Sources/Workspace+PanelLifecycle.swift
Workspace+PanelLifecycle.swift reached the 500-line budget threshold on
the speculative merge with main; move the cohesive sidebar status entry
visibility helpers into their own extension file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The planner suite's shared-state reset cancelled every entry in the
process-global restore-monitor registry, killing the serialized monitor
suite's in-flight tasks when suites interleaved (flaked
replacingOneTranscriptMonitorLeavesOtherTranscriptMonitorRunning).
Clean up only the suite's own registry entry, keyed by request ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 28699153 Deployed Jul 10, 2026 by vercel[bot]
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.

Agent hibernation destroys a running Claude session's transcript (.jsonl overwritten with metadata-only stub) — permanent data loss

2 participants