Skip to content

Fix stale Claude notification sidebar status - #6473

Merged
austinywang merged 8 commits into
mainfrom
issue-6441-stale-agent-status
Jun 22, 2026
Merged

austinywang merged 8 commits into
mainfrom
issue-6441-stale-agent-status

Conversation

@austinywang

@austinywang austinywang commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Root Cause

Claude notification hooks computed the live Claude PID, but the Needs input sidebar status write did not pass that PID into set_status. That left claude_code status entries without a matching agentPIDs entry, so the stale-PID sweep had nothing to probe after abrupt agent exits. Persisted agent status is also restart-unsafe because PIDs are not restorable; current restore code drops that runtime state, and this PR locks it with coverage.

Fix

  • Pass the already-resolved Claude PID into the notification setClaudeStatus call, matching the existing running-status paths.
  • Preserve the restore behavior that drops persisted agent status/PID/lifecycle runtime state rather than replaying status text after restart.

Tests

  • Added a CLI hook regression that runs the Claude notification hook against a mock socket and asserts the emitted set_status claude_code Needs input command includes --pid, which fails without the fix.
  • Added restore coverage proving a snapshotted claude_code status is serialized but not re-applied as live runtime state on session restore.

Local note: I reproduced the missing-PID command with a mock Unix socket and an existing tagged CLI because this task forbids local builds/xcodebuild. I did not run local xcodebuild/reload.

Closes #6441


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


Summary by cubic

Fixes stale Claude "Needs input" sidebar status by attaching the live PID to notification updates so the stale-PID sweep can clear dead statuses. Also keeps the CLI status command within the line-length budget and prevents restoring runtime agent status/PIDs/lifecycle after restart.

  • Bug Fixes
    • Pass the resolved Claude PID to the notification set_status call and keep the emitted command within the line-length budget.
    • Add tests: lifecycle test asserts --pid is present; session-restore test confirms persisted claude_code status and PIDs are not reapplied and lifecycle resets to unknown.

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

Review in cubic

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved session restoration to properly clear persisted agent status and related sidebar/agent runtime state.
  • New Features

    • Enhanced Claude notification handling to include process identifiers in status notifications, enabling more reliable stale-status cleanup after abrupt agent exits.
  • Tests

    • Added coverage to verify session restoration drops persisted agent sidebar status state.
    • Added coverage to ensure notification status commands include the expected process identifier.

@vercel

vercel Bot commented Jun 20, 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 Jun 22, 2026 11:02am
cmux-staging Building Building Preview, Comment Jun 22, 2026 11:02am

@coderabbitai

coderabbitai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fixes stale sidebar agent status by adding a pid field (sourced from claudePid) to the notify_target_async notification payload in cmux.swift. Adds two tests: one verifying the notification hook emits a --pid-bearing set_status command, and one verifying session snapshot restore drops persisted agent status entries and clears agent PID/panel index runtime state.

Changes

Stale agent status PID tracking fix

Layer / File(s) Summary
Notification payload includes pid field
CLI/cmux.swift
The notify_target_async payload construction now sets a pid field from claudePid alongside color, making the status PID-backed for the existing stale-PID sweep timer to reap.
Test infrastructure helper exports
cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift
Visibility of helper types and functions is relaxed from private to internal (ProcessRunResult, SurfaceFixture, ClaudeHookContext, MockSocketServerState, and helper functions) to enable reuse in new test files.
Notification hook PID and session-restore state cleanup tests
cmux.xcodeproj/project.pbxproj, cmuxTests/ClaudeNotificationStatusLifecycleTests.swift, cmuxTests/AgentSessionAutoResumeSwiftTests.swift
Registers ClaudeNotificationStatusLifecycleTests.swift in the project build; adds claudeNotificationStatusCarriesPIDForStaleSweep test asserting the notification hook emits --pid=<claudePID> in the set_status command; adds sessionRestoreDropsPersistedAgentStatusRuntimeState (with import CmuxSidebar) asserting a restored workspace drops sidebar status entries and clears all agent PID/index state.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • manaflow-ai/cmux#4562: Introduces CI/lint guard verifying all cmuxTests/*.swift files are wired into the cmuxTests target's PBXSourcesBuildPhase; directly validates this PR's new test file registration.

Poem

🐰 A PID without a home would linger and roam,
But now every status knows its process by name.
On restore, stale ghosts are swept clean and gone,
The sweep timer wakes and finds something to reap—
No more phantom "Needs input" from agents long asleep! ✨


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error PR introduces hardcoded English status strings ("Needs input", "Running", "Idle") in production CLI code without String(localized:) calls, violating full-internationalization rules for 20 supported... Wrap hardcoded status values in String(localized:defaultValue:) using existing catalog keys (e.g., "agent.generic.status.running", "feed.status.needsInput") to match established patterns elsewhere in the codebase.
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.
Cmux Swift Blocking Runtime ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing stale Claude notification sidebar status by passing PID to the status write and preventing restoration of dead agent status.
Linked Issues check ✅ Passed The PR addresses all four root causes identified in #6441: passing claudePid to setClaudeStatus, adding hook regression test verifying --pid inclusion, and adding restore test confirming persisted agent status/PID/lifecycle state are dropped.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the stale status issue: CLI payload extension, test additions for regression/restore coverage, access control adjustments in test helpers, and Xcode project updates.
Cmux Swift Actor Isolation ✅ Passed Production code change passes the PID parameter to existing function; test code adds helpers and @MainActor tests appropriately. No actor isolation violations introduced.
Cmux Expensive Synchronous Load ✅ Passed PR only adds pid parameter to existing setClaudeStatus call in CLI hook handler; no RestorableAgentSessionIndex.load() or expensive loaders added to main actor/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed PR passes cache-substitution check: passes fresh-read PID (from sessionStore.lookup or environment) to setClaudeStatus; explicitly does not restore persisted agent status/PIDs (tested); no cache su...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift source/test code and Xcode project files; no TypeScript, JavaScript, or shell scripts present, so runtime-no-hacky-sleeps rule is not applicable.
Cmux Algorithmic Complexity ✅ Passed Production change only adds pid parameter to existing O(1) function call with no loops/scans. Tests are explicitly exempt scaffolding under the rules.
Cmux Swift Concurrency ✅ Passed The PR introduces no legacy async patterns to production code. CLI/cmux.swift only adds a PID parameter to a synchronous setter. Test-only DispatchSemaphore usage for mock server synchronization is...
Cmux Swift @Concurrent ✅ Passed All Swift changes comply with concurrent annotation rules: setClaudeStatus and runClaudeHook remain synchronous without requiring @concurrent; new tests are synchronous with correct @MainActor anno...
Cmux Swift File And Package Boundaries ✅ Passed PR adds focused +2/-1 lines to CLI/cmux.swift within a 250-line budget for oversized files, plus test-only code in new/existing test files with clear single responsibilities and no mixed concerns.
Cmux Swiftpm Lockfiles ✅ Passed The PR contains no SwiftPM package, dependency, or .gitignore changes. Changes are limited to notification logic, test additions, and minimal test registration in the Xcode project file—none of whi...
Cmux Swift Logging ✅ Passed All print statements in production code are CLI command output (allowed per rules), and no logging diagnostics or violations detected in the PR changes.
Cmux User-Facing Error Privacy ✅ Passed Changes only affect internal socket commands (set_status with --pid flag) and test code. Process IDs are non-sensitive internal identifiers already used in set_agent_pid commands. No user-facing er...
Cmux Swiftui State Layout ✅ Passed PR contains only CLI code changes and test infrastructure; no SwiftUI View types, state declarations, or state layout violations present.
Cmux Architecture Rethink ✅ Passed This PR makes a small correctness fix with clear ownership and invariants. It adds the Claude PID parameter to status writes, enabling the existing stale-PID sweep to clear dead statuses. Test-only...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change standalone cmux-owned windows; only production change is a parameter addition to setClaudeStatus(), all new code is test-only fixtures which are allowed.
Cmux Source Artifacts ✅ Passed All 5 changed files are legitimate source, test, and configuration files: 4 Swift source/test files and 1 Xcode project config. None are artifacts, logs, caches, or build output.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PR adds functional changes only: pid parameter to setClaudeStatus calls. No test/debug seams added to production source; tests properly isolated in cmuxTests/.
Description check ✅ Passed The pull request description comprehensively covers the root cause, fix, testing approach, and constraints. It directly addresses the template's Summary and Testing sections with clear explanations of what changed and why, along with how the changes were tested.
✨ 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-6441-stale-agent-status

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a stale-sidebar-status bug where the Claude notification hook wrote a Needs input set_status command without a --pid flag, so the stale-PID sweep had no PID to probe when the agent exited abruptly. The fix is a one-line change adding the already-resolved claudePid to that call, matching what session-start and pre-tool-use already did.

  • Production fix (CLI/cmux.swift): adds pid: claudePid to the notification case's setClaudeStatus call. The function signature already had an optional pid parameter; all other callers are unaffected.
  • Regression test (ClaudeNotificationStatusLifecycleTests): runs the hook against a mock socket and asserts --pid=<claudePID> appears in the emitted command; helper types in ClaudeHookSurfaceResolutionSwiftTests are promoted from private to internal for intra-target reuse.
  • Restore coverage (AgentSessionAutoResumeSwiftTests): proves restoreSessionSnapshot drops persisted claude_code status entries, agent PIDs, and lifecycle state rather than re-applying them.

Confidence Score: 5/5

Safe to merge — the production change is a single-argument addition to an existing optional parameter, well covered by the new regression test.

The production change is minimal and self-contained: setClaudeStatus already accepted an optional pid and was doing the right thing for all other callers. Adding it to the notification case completes the pattern. The new tests directly validate the command shape and the restore invariant. No actor isolation, blocking primitive, or state representation issues were introduced.

No files require special attention.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds pid: claudePid to the notification hook's setClaudeStatus call, making it consistent with session-start and pre-tool-use. The function already accepted an optional PID; no other logic changed.
cmuxTests/ClaudeNotificationStatusLifecycleTests.swift New regression test that runs the notification hook against a mock socket and asserts the emitted set_status claude_code Needs input command includes --pid. Correctly reuses the harness helpers now promoted to internal in ClaudeHookSurfaceResolutionSwiftTests.
cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift Drops private from helper types and functions so that ClaudeNotificationStatusLifecycleTests can reuse them within the same test target. These are test-only types; no production source is touched.
cmuxTests/AgentSessionAutoResumeSwiftTests.swift Adds a @MainActor test proving that sessionSnapshot serialises claude_code status but restoreSessionSnapshot does not re-apply agent status, PIDs, or lifecycle state. Adds import CmuxSidebar to support assertions on Workspace.
cmux.xcodeproj/project.pbxproj Adds the new ClaudeNotificationStatusLifecycleTests.swift file reference and build-phase entry to the Xcode project. Mechanical and correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Claude as Claude Agent
    participant Hook as cmux CLI Hook
    participant Socket as cmux Socket Server
    participant Sweep as Stale-PID Sweep

    Claude->>Hook: notification event (CMUX_CLAUDE_PID set)
    Hook->>Hook: resolve claudePid from env/session
    Hook->>Socket: "set_status claude_code Needs input --pid=claudePid"
    Note over Socket: agentPIDs[claude_code] = claudePid
    Hook->>Socket: notify_target_async ...
    Claude--xHook: abrupt exit (no SessionEnd)
    Sweep->>Socket: probe agentPIDs[claude_code]
    Note over Sweep: Before fix: no PID, sweep skips,<br/>status stays stale forever
    Note over Sweep: After fix: PID present, process gone,<br/>sweep clears stale status
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 Claude as Claude Agent
    participant Hook as cmux CLI Hook
    participant Socket as cmux Socket Server
    participant Sweep as Stale-PID Sweep

    Claude->>Hook: notification event (CMUX_CLAUDE_PID set)
    Hook->>Hook: resolve claudePid from env/session
    Hook->>Socket: "set_status claude_code Needs input --pid=claudePid"
    Note over Socket: agentPIDs[claude_code] = claudePid
    Hook->>Socket: notify_target_async ...
    Claude--xHook: abrupt exit (no SessionEnd)
    Sweep->>Socket: probe agentPIDs[claude_code]
    Note over Sweep: Before fix: no PID, sweep skips,<br/>status stays stale forever
    Note over Sweep: After fix: PID present, process gone,<br/>sweep clears stale status
Loading

Reviews (6): Last reviewed commit: "Merge branch 'main' into issue-6441-stal..." | 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.

Actionable comments posted: 1

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

Inline comments:
In `@cmuxTests/AgentSessionAutoResumeSwiftTests.swift`:
- Around line 29-36: The assertion on line 36 uses sourcePanelId from the
pre-restore workspace when calling agentHibernationLifecycleState, but since
restoreSessionSnapshot(_:) remaps panel IDs, you need to use the
restored/remapped panel ID instead. Capture the panel ID that results from the
restoreSessionSnapshot(_:) call on the restored workspace, and use that remapped
panel ID in the agentHibernationLifecycleState(panelId:fallback:) assertion
instead of sourcePanelId to properly verify that the lifecycle state was cleared
on the restored panel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d86d471a-4ed1-4003-95aa-871aad893b20

📥 Commits

Reviewing files that changed from the base of the PR and between 22ab7f1 and 3ce8e1d.

📒 Files selected for processing (3)
  • CLI/cmux.swift
  • cmuxTests/AgentSessionAutoResumeSwiftTests.swift
  • cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift

Comment thread cmuxTests/AgentSessionAutoResumeSwiftTests.swift Outdated

This branch was successfully deployed

1 active deployment
Preview – cmux — cf0fdc59 Deployed Jun 22, 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.

Sidebar agent status (e.g. "Claude is waiting for your input") goes stale and is never cleared when the agent process dies

1 participant