Skip to content

Return tab to plain terminal icon when a live agent session exits - #7834

Closed
austinywang wants to merge 4 commits into
mainfrom
issue-7822-shell-icon-revert-claude
Closed

austinywang wants to merge 4 commits into
mainfrom
issue-7822-shell-icon-revert-claude

Conversation

@austinywang

@austinywang austinywang commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7822

Bug

Quitting a live agent session (Claude, Codex, OpenCode — any recognized agent) so the terminal returns to a plain shell left the tab still showing the agent brand icon. The title reverted to the shell cwd, the icon never did. Reproduced on nightly 0.64.17, which already includes #7740, so this was a remaining gap in that fix.

Root cause

A panel's restored (resumable) agent snapshot — restoredAgentSnapshotsByPanelId, the icon resolver's final fallback — had two unguarded lifecycle gaps:

  1. Unconditional adoption. Every session persist adopts the hook-index session into the panel's in-memory restored state (sessionPanelSnapshot) with no evidence the agent is still running. A persist running after the user quit the agent re-adopted the (still resumable) session at an idle prompt as .manualResumeAvailable and repainted the brand icon on a plain shell tab.
  2. Single exit signal. The only thing that invalidated the snapshot on a live exit was the shell-activity state machine, which needs a report_shell_state event to arrive after adoption. The proven-dead-PID prune (Fix stale terminal agent tab icons #7740's title path) cleared live agent runtime but left the snapshot driving the icon.

Fix

New Sources/Workspace+RestoredAgentLifecycle.swift centralizes the snapshot's lifecycle rules:

  • adoptIndexedRestorableAgentSnapshot gates fresh persist-time adoption on evidence the session is current: recorded agent runtime or a running foreground command. Invalidated fingerprints keep clearing instead of re-adopting.
  • invalidateRestoredAgentSnapshotForProvenAgentExit treats a stale-agent prune (title update, submit-action refresh, or the 30s PID sweep — all proofs the agent process died) as an observed agent exit, invalidating the snapshot exactly like the shell-activity machine does. Pending auto-resume (.awaitingAutoResumeCommand), hibernated panels, and remote workspaces are exempt.

App-relaunch restores never record agent PIDs, so seeded manual-resume/hibernation brand icons are untouched by construction. The invariant now holds for every agent kind: a plain-shell tab with no live agent and no pending restore shows the plain terminal icon. This also un-sticks the text box's agent-bound submit context after an exit (same underlying map).

Tests

Two-commit structure so CI proves the tests catch the bug — commit 1 adds cmuxTests/TerminalTabAgentIconRestoredSnapshotTests.swift (wired into pbxproj) and should be red, commit 2 adds the fix:

  • quittingLiveAgentReturnsTabToPlainTerminalIcon (parameterized: Claude/Codex/OpenCode) — dead recorded runtime + populated restored snapshot + updatePanelTitle title revert must clear the icon with no shell-activity event. Fails without the fix.
  • sessionPersistAfterAgentExitDoesNotPaintAgentIconOnPlainShell — real sessionSnapshot persist over an on-disk claude hook-store fixture at an idle prompt must not adopt/repaint. Fails without the fix.
  • Guards: persist while the agent command runs still adopts (relaunch resume keeps working); app-relaunch manual-resume and pending auto-resume panels keep their brand icon through title churn and stale-runtime prunes.

Localization audit: no user-facing strings added or changed (state/icon plumbing only). Swift file budget respected (Workspace.swift shrank by 20 lines; new files are 73/211 lines); no budget TSV touched; no new warnings in touched files.

🤖 Generated with Claude Code


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


Note

Medium Risk
Touches terminal tab icon resolution and restored-agent resume state across persist, title updates, and PID pruning; behavior is guarded by targeted tests but spans several lifecycle edge cases (auto-resume, relaunch, Dock).

Overview
Fixes #7822: after a live agent quits to a plain shell, the tab icon returns to the generic terminal mark instead of keeping the agent brand, while the resumable session snapshot stays for manual resume, relaunch, and forking.

Introduces RestoredAgentResumeState.recordedSessionOnly so “session recorded” and “tab should show agent branding” are separate. Persist-time adoption in Workspace+RestoredAgentLifecycle now sets .observedAgentCommandRunning only when the shell reports a running foreground command; otherwise adoption uses .recordedSessionOnly so a post-exit persist does not repaint the brand on an idle prompt. downgradeRestoredAgentSnapshotForProvenAgentExit runs when stale agent PIDs are pruned (including via updatePanelTitle) and moves eligible panels to .recordedSessionOnly without dropping the snapshot; pending auto-resume, hibernated panels, and remote workspaces are exempt.

TerminalTabAgentIcon (workspace and Dock) ignores the restored snapshot for icon resolution when resume state is .recordedSessionOnly. Regression coverage lives in TerminalTabAgentIconRestoredSnapshotTests.

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


Summary by cubic

Fixes #7822. When a live agent exits, the tab icon now reverts to the plain terminal icon even without a shell-activity event, while keeping the exited session resumable. Applies to Claude, Codex, and OpenCode.

  • Bug Fixes

    • Introduced RestoredAgentResumeState.recordedSessionOnly and updated icon resolvers to ignore restored snapshots in this state, so plain-shell tabs don’t show agent branding.
    • Persist-time adoption now always records the snapshot but only brands the tab when a foreground command is running; otherwise it marks .recordedSessionOnly so manual resume/relaunch/fork stay intact without repainting the icon.
    • Proven agent exit (title/submit refresh prune or periodic PID sweep) downgrades the state to .recordedSessionOnly and syncs the icon; pending auto-resume, hibernated panels, and remote workspaces are exempt. Centralized in Workspace+RestoredAgentLifecycle.swift via adoptIndexedRestorableAgentSnapshot and downgradeRestoredAgentSnapshotForProvenAgentExit. Added regression tests for exit, persist-after-exit, live persist, manual resume, and pending auto-resume.
  • Refactors

    • Normalized project.pbxproj entry ordering.

Written for commit 42c29a2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved restoration of agent sessions and terminal tab icons after app relaunches.
    • Prevented stale or exited agent sessions from incorrectly restoring agent branding on plain shell tabs.
    • Preserved correct agent icons for pending, manually resumable, and actively running sessions.
    • Enhanced cleanup of stale agent processes to also invalidate related restore metadata.
  • Tests
    • Added new regression tests covering terminal tab agent icon restoration across multiple agent lifecycle and relaunch scenarios.

austinywang and others added 2 commits July 10, 2026 02:09
…minal icon

Covers #7822 for Claude, Codex, and
OpenCode through the real Workspace state path:

- quitting a live agent (dead recorded runtime + title revert to the shell
  cwd) must clear the restored-snapshot brand icon even when no shell-activity
  prompt event arrives
- a session persist running after the agent already exited (idle prompt, no
  live runtime) must not adopt the indexed session and repaint the brand icon
  on a plain shell tab
- guards: persist-time adoption while the agent command runs keeps working,
  and app-relaunch manual-resume / pending auto-resume panels keep their
  brand mark

The exit-path expectations fail without the fix (next commit).

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

Root cause: a panel's restored (resumable) agent snapshot — the icon
resolver's final fallback — had two unguarded lifecycle gaps, so a tab kept
its agent brand icon (Claude, Codex, OpenCode, all kinds) after the user quit
the agent back to a plain shell:

- Session persists adopt the hook-index session into
  restoredAgentSnapshotsByPanelId without any evidence the agent is still
  running, so a persist after the exit repainted the brand icon onto an idle
  prompt as .manualResumeAvailable.
- The only exit signal that invalidated the snapshot was the shell-activity
  state machine, which needs a report_shell_state event to arrive after
  adoption; the proven-dead-PID prune (#7740) cleared live runtime but left
  the snapshot driving the icon.

Fix (Workspace+RestoredAgentLifecycle.swift):

- adoptIndexedRestorableAgentSnapshot gates fresh persist-time adoption on
  recorded agent runtime or a running foreground command, and keeps honoring
  invalidated fingerprints.
- invalidateRestoredAgentSnapshotForProvenAgentExit treats a stale-agent
  prune (title update, submit-action refresh, or the 30s PID sweep) as an
  observed agent exit and invalidates the snapshot, exactly like the
  shell-activity machine does. Pending auto-resume, hibernated panels, and
  remote workspaces are exempt, and app-relaunch restores never record PIDs,
  so seeded manual-resume icons are untouched.

The invariant now holds for every agent kind: a plain-shell tab with no live
agent and no pending restore shows the plain terminal icon.

Closes #7822

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

vercel Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 10, 2026 4:52pm
cmux-staging Building Building Preview, Comment Jul 10, 2026 4:52pm

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Restored agent lifecycle

Layer / File(s) Summary
Snapshot adoption and restore integration
Sources/Workspace+RestoredAgentLifecycle.swift, Sources/Workspace.swift, cmux.xcodeproj/project.pbxproj
Centralizes indexed snapshot validation, resume-state setup, invalidation handling, and terminal icon synchronization.
Proven-exit invalidation during runtime cleanup
Sources/Workspace+PanelLifecycle.swift, Sources/Workspace+RestoredAgentLifecycle.swift
Invalidates eligible restored snapshots when stale agent PID ownership is removed.
Lifecycle regression coverage and project wiring
cmuxTests/TerminalTabAgentIconRestoredSnapshotTests.swift, cmux.xcodeproj/project.pbxproj
Adds coverage for quitting, persistence, relaunch, manual resume, auto-resume, and stale-runtime pruning, and registers the new source files.

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

Sequence Diagram(s)

sequenceDiagram
  participant SessionRestore
  participant Workspace
  participant PanelLifecycle
  participant TerminalTab
  SessionRestore->>Workspace: provide indexed restorable snapshot
  Workspace->>Workspace: adopt snapshot and derive resume state
  Workspace->>TerminalTab: synchronize agent brand icon
  PanelLifecycle->>Workspace: prune stale agent PID ownership
  Workspace->>Workspace: invalidate restored snapshot for proven exit
  Workspace->>TerminalTab: restore plain terminal icon
Loading

Possibly related issues

  • manaflow-ai/cmux issue 7822 — Addresses restored-agent snapshot fallback and icon persistence after quitting to a plain shell, including stale PID invalidation.

Possibly related PRs

  • manaflow-ai/cmux#6803 — Retained restorable snapshots with stale PIDs are directly involved in this PR’s snapshot invalidation behavior.
  • manaflow-ai/cmux#7607 — Restored snapshot state directly affects terminal tab agent icon resolution.
  • manaflow-ai/cmux#7740 — Both changes connect stale PID pruning with terminal icon state.
🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
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.
Cmux Swift Actor Isolation ✅ Passed PASS: Workspace is @MainActor, and the new lifecycle helpers/callers mutate only main-actor state; no new Sendable or background-access isolation debt was introduced.
Cmux Swift Blocking Runtime ✅ Passed PASS: Touched Swift changes add no semaphores, waits, sleeps, locks, or main-queue sync; the new lifecycle code only mutates snapshot/icon state.
Cmux Browser Automation Off-Main ✅ Passed The PR only changes workspace/agent snapshot lifecycle and tests; no browser.* socket automation routing, WebKit/AppKit worker-lane commands, or policy-debt code was touched.
Cmux Expensive Synchronous Load ✅ Passed No new expensive sync load was added; the only RestorableAgentSessionIndex.load() stays the existing cold-cache fallback behind SharedLiveAgentIndex.shared.
Cmux Cache Substitution Correctness ✅ Passed The snapshot path still uses the fresh hook index and the restored-agent cache is event-driven, cold-checked, and invalidated on proven exit.
Cmux No Hacky Sleeps ✅ Passed Only the Xcode project file changed here; no covered non-Swift runtime code added sleeps, timers, polling, or wall-clock waits.
Cmux Algorithmic Complexity ✅ Passed New code uses single-pass per-panel PID scans and O(1) lookups; no nested rescans, repeated sorts, or hot-path superlinear work were added.
Cmux Swift Concurrency ✅ Passed The patch only adds synchronous lifecycle/icon-state logic and tests; it introduces no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns.
Cmux Swift @Concurrent ✅ Passed The diff adds only synchronous @MainActor Workspace helpers and a sync test fixture; no new nonisolated async work or @concurrent misuse.
Cmux Swift File And Package Boundaries ✅ Passed Focused bug fix: new prod helper is 73 lines, Workspace.swift shrank, and the added test file is exempt; responsibilities stay cohesive.
Cmux Swiftpm Lockfiles ✅ Passed Only project.pbxproj entry ordering changed; no Package.swift, .gitignore, package-reference, or lockfile pin changes were made.
Cmux Swift Logging ✅ Passed No added or changed runtime Swift logging appears in the patched hunks; the new lifecycle file and touched workspace code add no print/debugPrint/dump/NSLog/Logger calls.
Cmux User-Facing Error Privacy ✅ Passed The diff only changes lifecycle logic, tests, and project wiring; no user-facing error/alert/copy text was added or altered, and the remaining strings are internal comments/debug logs.
Cmux Full Internationalization ✅ Passed Touched production files only change restored-agent lifecycle logic; no new user-facing text, catalogs, or locale assets were added or edited.
Cmux Swiftui State Layout ✅ Passed PASS: the PR only adds workspace lifecycle/model code and tests; no new SwiftUI state/layout patterns were introduced, and existing @Published state is legacy/incidental.
Cmux Architecture Rethink ✅ Passed PASS: The diff centralizes restored-agent lifecycle in Workspace, names the invariant, uses no timing/lock/observer hacks, and adds tests for the exit/persist paths.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only changes Workspace lifecycle and tests; no NSWindow/NSPanel/NSWindowController/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes were introduced.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/test/config files; no temp, build, cache, or generated artifact paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The new helpers are production lifecycle utilities with production callers; no test/debug-only seam or test hook was added to Sources.
Cmux No Ambient Global State ✅ Passed The new behavior lives on extension Workspace; no new file-scope func/var, namespace type, or singleton was added in production Swift.
Title check ✅ Passed The title clearly matches the main fix: returning a tab to the plain terminal icon after a live agent exits.
Description check ✅ Passed The description covers the bug, root cause, fix, and testing, but it omits the template's demo video, review trigger, and checklist sections.
✨ 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-7822-shell-icon-revert-claude

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 Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes terminal tab agent icons after a live agent exits. The main changes are:

  • Adds a restored-agent lifecycle helper for adoption and stale-PID downgrade.
  • Adds .recordedSessionOnly so exited sessions can stay resumable without branding the tab.
  • Updates tab icon resolution for workspace and detached-surface state.
  • Adds tests for live exit, persist-after-exit, and restore cases.

Confidence Score: 4/5

This is close, but one lifecycle case should be fixed before merging.

  • The new recorded-session state can still be cleared by a later shell command.
  • That can remove the resumable session after the icon has correctly returned to plain terminal.

Sources/Workspace.swift

Important Files Changed

Filename Overview
Sources/TerminalTabAgentIcon.swift Suppresses restored snapshot branding when a session is recorded but not running.
Sources/Workspace+PanelLifecycle.swift Routes stale agent PID pruning through the restored-agent downgrade path.
Sources/Workspace+RestoredAgentLifecycle.swift Centralizes restored-agent adoption and downgrade behavior.
Sources/Workspace.swift Adds the recorded-session-only state and updates shell activity transitions.
cmux.xcodeproj/project.pbxproj Wires the new production file and test file into the Xcode project.
cmuxTests/TerminalTabAgentIconRestoredSnapshotTests.swift Adds tests for restored-agent icon and resume lifecycle behavior.

Reviews (3): Last reviewed commit: "Keep exited agent sessions resumable; ga..." | Re-trigger Greptile

Comment on lines +67 to +68
restoredAgentResumeStatesByPanelId[panelId] != .awaitingAutoResumeCommand,
(panels[panelId] as? TerminalPanel)?.isAgentHibernated != true else {

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.

P2 Manual Resume Snapshot Pruned

When a manually resumable restored panel also has a stale recorded PID, the new prune path treats that dead PID as proof of a live-agent exit and clears the restored snapshot. That can remove the intended manual-resume brand icon and resume state during title churn or the PID sweep, even though pending auto-resume and hibernated panels are protected from the same stale-runtime case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the designed invalidation, not a regression — the case where .manualResumeAvailable coexists with recorded agent runtime can only arise when an agent actually ran live in this panel during this app run and its process died:

  • App-relaunch restore and hibernation never record agent PIDs (seedSessionRestoredAgentIconState seeds no runtime; enterAgentHibernation clears all PIDs before setting .manualResumeAvailable), so the intended manual-resume brand icon has no recorded PID to prune — covered by relaunchRestoredManualResumePanelKeepsBrandIcon.
  • If a recorded PID exists and is proven dead, the agent ran and exited here. The shell-activity machine already invalidates .manualResumeAvailable on any observed commandRunning (Workspace.swift, updateRestoredAgentResumeState), so pruning on proven process death applies the same semantics through the runtime signal — this is the Tab keeps Claude icon after quitting to plain shell — restored-agent snapshot fallback survives #7740 #7822 fix itself (mid-run adoption can park the state at .manualResumeAvailable when shell integration is absent).
  • .awaitingAutoResumeCommand stays protected because there the recorded runtime provably belongs to a previous run, never to the queued resume — covered by pendingAutoResumePanelKeepsBrandIconThroughStaleRuntimePrune.

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

@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/Workspace+PanelLifecycle.swift (1)

170-188: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gate snapshot invalidation on proven process exit

The guard let pid branch clears ownership and sets didChange without checking process liveness. This state is reachable because adoptDetachedAgentRuntimeState intentionally restores agentPIDKeys that may have no corresponding PID. Line 188 then invalidates the restored snapshot even though no exit was proven, potentially removing a valid manual-restore snapshot and its brand icon.

Track didProveAgentExit separately and set it only when a non-nil PID fails isRecordedAgentPIDLive.

As per path instructions, correctness-critical exit decisions must use authoritative running/exited evidence and fail closed when that signal is missing.

Proposed fix
     let keys = agentPIDKeysByPanelId[panelId] ?? []
     var didChange = false
+    var didProveAgentExit = false
     for key in keys {
         guard let pid = agentPIDs[key] else {
             if clearAgentPID(key: key, panelId: panelId, clearStatus: true, refreshPorts: false) {
                 didChange = true
             }
             continue
         }
         if !isRecordedAgentPIDLive(key: key, pid: pid),
            clearAgentPID(key: key, panelId: panelId, clearStatus: true, refreshPorts: false) {
             didChange = true
+            didProveAgentExit = true
         }
     }
     if didChange {
         if refreshPorts { refreshTrackedAgentPorts() }
         AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: id, surfaceId: panelId)
-        invalidateRestoredAgentSnapshotForProvenExit(panelId: panelId)
+        if didProveAgentExit {
+            invalidateRestoredAgentSnapshotForProvenExit(panelId: panelId)
+        }
     }
🤖 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/Workspace`+PanelLifecycle.swift around lines 170 - 188, Gate
restored-snapshot invalidation in clearStaleAgentPIDs on proven process exit:
add a separate didProveAgentExit flag, set it only when a non-nil PID fails
isRecordedAgentPIDLive, and leave it false when the guard let pid branch clears
ownership without liveness evidence. Continue tracking didChange for cleanup and
port refresh, but call invalidateRestoredAgentSnapshotForProvenAgentExit only
when didProveAgentExit is true.

Source: Path instructions

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

Outside diff comments:
In `@Sources/Workspace`+PanelLifecycle.swift:
- Around line 170-188: Gate restored-snapshot invalidation in
clearStaleAgentPIDs on proven process exit: add a separate didProveAgentExit
flag, set it only when a non-nil PID fails isRecordedAgentPIDLive, and leave it
false when the guard let pid branch clears ownership without liveness evidence.
Continue tracking didChange for cleanup and port refresh, but call
invalidateRestoredAgentSnapshotForProvenAgentExit only when didProveAgentExit is
true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 496b5ede-87bd-48ff-918e-ae8c627bcf51

📥 Commits

Reviewing files that changed from the base of the PR and between b7c3498 and 5592a28.

📒 Files selected for processing (5)
  • Sources/Workspace+PanelLifecycle.swift
  • Sources/Workspace+RestoredAgentLifecycle.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/TerminalTabAgentIconRestoredSnapshotTests.swift

Comment on lines +67 to +68
restoredAgentResumeStatesByPanelId[panelId] != .awaitingAutoResumeCommand,
(panels[panelId] as? TerminalPanel)?.isAgentHibernated != true else {

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.

P1 Manual resume still clears

This still clears manually resumable restored sessions when they also carry a stale recorded PID. seedSessionRestoredAgentIconState(... willRunStartupCommand: false, willRunStartupInput: false) marks app-relaunch manual-resume panels as .manualResumeAvailable, but this guard only exempts .awaitingAutoResumeCommand and hibernation. If restored runtime state includes a leftover PID from the previous run, the next title update or PID sweep prunes that stale PID and reaches this path, clearing the restored snapshot and removing the intended manual-resume icon and resume state. The fix needs to distinguish restore-seeded manual-resume snapshots from live snapshots adopted during the current run, or otherwise preserve the manual-resume stale-runtime case without keeping the post-exit live-session icon.

Rule Used: Flag correctness-critical detection/identity deriv... (source)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 42c29a2 by reworking the mechanism: the prune no longer clears the snapshot at all. It downgrades the resume state to the new .recordedSessionOnly, which keeps the session recorded — persisted for relaunch, manually resumable, forkable — and only stops it from branding the tab icon. AgentSessionAutoResumeSettingsTests (exited-session persistence) and the updated regression tests (quittingLiveAgentReturnsTabToPlainTerminalIcon now asserts the snapshot survives with .recordedSessionOnly) pin both halves. Seeded manual-resume panels remain untouched: restore and hibernation never record agent PIDs, so the prune cannot fire on them.

@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 a1fc84b. Configure here.

Comment thread Sources/Workspace+RestoredAgentLifecycle.swift Outdated
The first cut of the #7822 fix blocked persist-time snapshot adoption at an
idle prompt and cleared snapshots on proven agent exit. That broke designed
behavior pinned by AgentSessionAutoResumeSettingsTests: an exited agent
session must stay persisted for manual resume after relaunch and remain
forkable — only auto-resume (wasAgentRunning=false) and, per #7822, the tab
brand icon may not follow it.

Rework: RestoredAgentResumeState gains .recordedSessionOnly — the session is
recorded and resumable but the agent is known not to be running in this
panel. Persist-time adoption assigns it whenever the agent is not the running
foreground command, and the proven-dead-PID prune downgrades
.manualResumeAvailable/.observedAgentCommandRunning to it instead of clearing
the snapshot. The icon resolvers (Workspace and Dock transfer paths) skip the
restored-snapshot fallback in that state; everything else (persistence,
fork, manual resume, hibernation eligibility, shell-activity state machine
parity) behaves exactly as before.

Regression tests updated to pin both halves: the icon reverts to the plain
terminal icon on exit AND the session snapshot survives for manual resume.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread Sources/Workspace.swift
Comment on lines 4597 to 4600
case .some(.autoResumeCommandRunning), .some(.observedAgentCommandRunning):
break
case .some(.manualResumeAvailable), nil:
case .some(.manualResumeAvailable), .some(.recordedSessionOnly), nil:
invalidateRestoredAgentSnapshot(panelId: panelId, restoredAgent: restoredAgent)

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.

P1 Preserved session clears When an exited agent has been downgraded to .recordedSessionOnly, the snapshot is supposed to stay available for manual resume, relaunch persistence, and forking while no longer branding the tab. This branch still sends that state through invalidateRestoredAgentSnapshot on the next .commandRunning shell report. A normal flow can hit this after the agent exits, the stale PID prune downgrades the panel, and the user later runs any shell command; the saved restorable session is then removed entirely instead of only hiding its icon.

Suggested change
case .some(.autoResumeCommandRunning), .some(.observedAgentCommandRunning):
break
case .some(.manualResumeAvailable), nil:
case .some(.manualResumeAvailable), .some(.recordedSessionOnly), nil:
invalidateRestoredAgentSnapshot(panelId: panelId, restoredAgent: restoredAgent)
case .some(.autoResumeCommandRunning), .some(.observedAgentCommandRunning),
.some(.recordedSessionOnly):
break
case .some(.manualResumeAvailable), nil:
invalidateRestoredAgentSnapshot(panelId: panelId, restoredAgent: restoredAgent)

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 42c29a2c 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

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tab keeps Claude icon after quitting to plain shell — restored-agent snapshot fallback survives #7740

3 participants