Skip to content

Clear agent Running status on interrupt signals - #4390

Closed
austinywang wants to merge 27 commits into
mainfrom
issue-4389-running-status-after-interrupt
Closed

austinywang wants to merge 27 commits into
mainfrom
issue-4389-running-status-after-interrupt

Conversation

@austinywang

@austinywang austinywang commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • install Claude Code StopFailure as a turn-end hook and normalize it through the same status-clearing path as Stop
  • keep StopFailure from sending a misleading completion notification while still clearing Running to Idle
  • have the Codex transcript monitor publish Idle and update hook session runtime state when a turn reaches healthy terminal completion without a Stop hook

Related

Fixes #4389. This also gives Claude StopFailure the missing status transition noted in #2488; Codex missing-Stop cases like #4276 are covered when the transcript reaches a terminal healthy completion.

Testing

  • Added regression tests in a separate first commit.
  • Not run locally per repo policy; CI will run the test suite.

Note

Medium Risk
Changes agent hook lifecycle, shared workspace status, and Codex transcript monitor concurrency guards—user-visible status can be wrong if turn/session matching regresses, but behavior is heavily regression-tested.

Overview
Fixes stale Running status when turns end without a normal Stop hook—especially after interrupts.

Claude: Adds a stop-failure hook path wired like Stop for status/session cleanup, but skips completion summarization so failed/interrupted turns don’t look like a finished response. The cmux-claude-wrapper installs StopFailure hooks; feed/telemetry treat StopFailure like Stop (non-actionable).

Codex monitor: When transcript parsing sees a healthy terminal completion without Stop, the monitor now records idle runtime state, clears panel-scoped lifecycle, and publishes Idle only when guards pass (current turn, no retired lease, no other live session on the same surface/workspace as appropriate). User-input and failure paths also persist hook session state with --cwd. Transcript scanning resets on new task_started and avoids treating old completed turns as the current one when monitoring unscoped turns.

Session “running” checks: hasRunningSession treats needs input as active on a surface; idle suppression considers other sessions per surface, not only workspace-wide.

Tests / i18n: Regression coverage for wrapper StopFailure, feed classification, and Codex monitor idle/race behavior; localized Claude hook help includes stop-failure.

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


Summary by cubic

Clears stale Running after interrupts by treating Claude stop-failure as a turn end and by having the Codex monitor set Idle on clean terminal completion, scoped by surface and guarded against stale/racing sessions. Preserves shared workspace status while clearing panel lifecycle and keeps Idle updates current by validating the active turn. Fixes #4389.

  • Bug Fixes
    • Claude: Add stop-failure subcommand that clears Running via the Stop path but skips completion; wrapper installs it; feed/telemetry map to Stop; CLI help includes stop-failure and is localized.
    • Status/idle: Count Needs Input as active on the same surface; persist runtime state with --cwd; record runtime status and publish Needs Input/Error; Codex monitor sets panel Idle only for the current turn, only if no other live same-surface session, and if the lease isn’t retired; clears panel lifecycle and publishes shared Idle only if no other live workspace session; reset on new task_started; ignore old terminal turns and keep watching the current unscoped turn.
    • Tests: Regressions for stop-failure wiring/telemetry and wrapper hook; Codex Idle without Stop with current-turn guard, stale-session suppression, live-process guard, ignoring old terminal turns, unscoped-current pending, and preserving shared status while another panel is running.

Written for commit 3eeee97. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Treats a stop-failure Claude hook as a turn-end/stop signal and suppresses completion summarization for it
    • Monitor runtime reporting now records session, workspace, surface, and working-directory context and avoids duplicate idle notifications across concurrent sessions
  • Documentation

    • Wrapper docs updated to include the StopFailure hook
  • Tests

    • Integration tests added to validate stop-failure hook wiring and monitor idle/status behavior

Review Change Stack

@vercel

vercel Bot commented May 19, 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 8, 2026 7:47am
cmux-staging Building Building Preview, Comment Jul 8, 2026 7:47am

@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown

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 stop-failure as a turn-end signal routed like stop; updates the Claude wrapper and help text; extends Codex monitor to persist runtime-status, thread session/workspace/surface/cwd/env through publishers, suppress duplicate idle notifications, and adds integration tests.

Changes

Stop-Failure Hook and Monitor Session State

Layer / File(s) Summary
Stop-Failure Hook Routing and Status Mapping
CLI/CMUXCLI+AgentHookDefinitions.swift, CLI/cmux.swift, Resources/Localizable.xcstrings
Route stop-failure to AgentHookAction.stop; treat stop-failure as a terminal turn boundary that skips completion summarization; update help/usage text and internal stop/runtime-status mappings to include stop-failure/StopFailure.
Wrapper Hook Configuration and Documentation
Resources/bin/claude
Document StopFailure in the wrapper comments and update the HOOKS_JSON emitted configuration to include the stop-failure hook wiring.
Codex Monitor Session State Helpers
CLI/cmux.swift
Extend hasRunningSession to accept surfaceId/excludingSessionId and include .needsInput; add helpers to map notification statuses to runtime statuses, derive a codex session-store path from environment, upsert runtime-status, and detect other running sessions.
Codex Monitor Context Threading and Publisher Updates
CLI/cmux.swift
Parse --cwd; thread cwd and env through monitor flows; update publishCodexMonitorIdle, publishCodexMonitorUserInput, and publishCodexMonitorFailure to accept sessionId/workspaceId/surfaceId/cwd/env, record runtime-status via shared helper, and return early when the monitor lease is retired.
Integration Tests
cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift, cmuxTests/WorkspaceRemoteConnectionTests.swift
Add test verifying the Claude wrapper installs a StopFailure turn-end hook wired to hooks claude stop-failure; add test ensuring codex monitor publishes Idle on a healthy terminal completion when another running session exists.

Sequence Diagram:

sequenceDiagram
  participant CLAUDE_WRAPPER
  participant CMUX_CLI
  participant RUNTIME_STORE
  participant SOCKET
  CLAUDE_WRAPPER->>CMUX_CLI: emit hooks claude stop-failure
  CMUX_CLI->>RUNTIME_STORE: upsert runtimeStatus = Stop/StopFailure (include session/workspace/surface/cwd/env)
  CMUX_CLI->>SOCKET: send scoped set_status / turn-end notification
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

"🐇
I hop through hooks at break of day,
stop-failure now ends the play,
sessions tuck their state to rest,
idle signals pass the test,
sidebar calms—no more stray."


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces 13 DispatchSemaphore instances with 9 blocking semaphore.wait() calls plus 6 NSLock and flock patterns in production CLI/cmux.swift, violating swift-blocking-runtime.md rule. Replace DispatchSemaphore blocking waits with async/await patterns, callbacks, or notifications; use actors for NSLock; use FileManager APIs instead of flock for ClaudeHookSessionStore state management.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers summary and testing, but it omits the Demo Video, Review Trigger, and Checklist sections from the template. Add the missing sections, including a demo video or N/A note, the review trigger block, and the checklist items.
✅ Passed checks (14 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The pull request successfully addresses all coding objectives from issue #4389: installing StopFailure hook routing through Stop, clearing Running status without spurious completion notifications, and updating Codex monitor to publish Idle on healthy terminal completion.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing #4389 and related issues (#2488, #4276). Updates to CLI commands, hook routing, Codex monitoring, localization, and tests are all justified by the linked objectives.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation violations found. New functions use file-locked session stores, create fresh instances, and run synchronously without crossing concurrency boundaries.
Cmux No Hacky Sleeps ✅ Passed Non-Swift code (Resources/bin/claude shell script) contains no hacky sleeps or polling patterns for race fixes. Sleeps in Swift code excluded (covered by swift-blocking-runtime rule).
Cmux Swift Concurrency ✅ Passed DispatchQueue used only for OS boundaries (DispatchSource, signals). Tests use withCheckedContinuation. No fire-and-forget Tasks, Combine, or completion handlers added.
Cmux Swift @Concurrent ✅ Passed PR uses Swift 5.0 (not Swift 6), adds only synchronous functions without @concurrent annotations, and makes no changes that trigger the swift-concurrent-annotation rules.
Cmux Swift File And Package Boundaries ✅ Passed 162 lines added to CLI/cmux.swift (under 250-line threshold for bug fixes). New CLI/CMUXCLI+AgentHookDefinitions.swift has 578 lines with clear single responsibility. Both pass boundary rules.
Cmux Swift Logging ✅ Passed No logging violations found. PR changes use sendV1Command for socket output, no print/NSLog in app code, no MainActor Logger issues, and tests are exempt from logging restrictions.
Cmux User-Facing Error Privacy ✅ Passed New CLI help text and code changes contain only public subcommands/flags; no vendor names, internal provider details, credentials, or sensitive data exposed.
Cmux Full Internationalization ✅ Passed String "cli.claude-hook.help" uses String(localized:) API with full Localizable.xcstrings translations for all 19 supported locales.
Cmux Swiftui State Layout ✅ Passed PR contains only CLI/backend code and tests; no SwiftUI changes, Observable state, or layout code modifications detected. Check not applicable.
Cmux Architecture Rethink ✅ Passed Clear ownership via ClaudeHookSessionStore, atomic file-based locking, FSEvents monitoring, stop-failure reuses .stop path. No timing repairs, duplicate owners, or split lifecycle ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains only test-only NSWindow fixtures with proper cmux.main.* identifiers in test files. No production code adds or materially changes standalone auxiliary windows.
Title check ✅ Passed The title clearly matches the main fix: clearing Running status after interrupts.
✨ 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-4389-running-status-after-interrupt

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 May 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR clears stale agent running status after interrupted or terminal turns. The main changes are:

  • Adds a Claude StopFailure hook that uses the stop cleanup path.
  • Skips completion notifications for failed Claude stops.
  • Lets the Codex transcript monitor publish Idle after healthy terminal completion.
  • Records Codex monitor runtime status for idle, needs-input, and error states.
  • Adds tests for StopFailure hook wiring and Codex monitor idle behavior.

Confidence Score: 5/5

This looks safe to merge.

  • The StopFailure hook now checks for the exact stop-failure command.
  • The Claude stop-failure path clears status without creating a completion notification.
  • No blocking issues were found in the changed code that fit this follow-up review scope.

Important Files Changed

Filename Overview
Resources/bin/cmux-claude-wrapper Adds the Claude StopFailure hook command and keeps feed telemetry for that hook.
CLI/cmux.swift Handles stop-failure through the stop path and adds Codex monitor idle/runtime-state updates.
CLI/CMUXCLI+AgentHookDefinitions.swift Registers stop-failure as a stop-style Claude hook action.
CLI/FeedEventClassifier.swift Classifies Claude StopFailure as non-actionable stop response telemetry.
Resources/Localizable.xcstrings Adds localized CLI help text for the updated Claude hook usage.
cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift Adds coverage that requires the StopFailure hook to call the stop-failure subcommand.
cmuxTests/WorkspaceRemoteConnectionTests.swift Adds Codex monitor tests for idle publication and concurrent-session suppression.

Reviews (17): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 2026

@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 `@CLI/cmux.swift`:
- Around line 19319-19333: The guard in
hasOtherRunningCodexMonitorSession(sessionId:workspaceId:env:) only excludes by
workspaceId which prevents panel-scoped idle updates; update the function
signature to accept an optional surfaceId (e.g., surfaceId: String?) and change
the check to dedupe by surfaceId when provided (call
codexHookSessionStore(...).hasRunningSession with both workspaceId and
surfaceId), and only perform the workspace-wide exclusion when surfaceId == nil;
also update callers (e.g., publishCodexMonitorIdle) to pass the surfaceId so
panel-scoped set_status events aren’t suppressed.
🪄 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: e157f6c4-9327-4220-9abb-17cd2de96816

📥 Commits

Reviewing files that changed from the base of the PR and between 4028438 and d074d14.

📒 Files selected for processing (5)
  • CLI/CMUXCLI+AgentHookDefinitions.swift
  • CLI/cmux.swift
  • Resources/bin/claude
  • cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
  • cmuxTests/WorkspaceRemoteConnectionTests.swift

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Re-trigger cubic

coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 2026

@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: 2

🤖 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 `@CLI/cmux.swift`:
- Around line 663-679: The current
hasRunningSession(workspaceId:surfaceId:excludingSessionId:) only treats
sessions with record.runtimeStatus == .running as active, which allows a
concurrent session in .needsInput to be ignored and let another publish Idle;
update the predicate inside withLockedState so it treats .needsInput as an
active state as well (i.e., consider record.runtimeStatus == .running ||
record.runtimeStatus == .needsInput) when deciding whether a same-scope session
exists, keeping the existing workspace/surface/excludingSessionId checks
(function: hasRunningSession, property: record.runtimeStatus).

In `@cmuxTests/WorkspaceRemoteConnectionTests.swift`:
- Around line 3071-3115: The test races because startMockServerAccepting runs
async and you assert on state.commands immediately; update the test (the block
using startMockServerAccepting, then runProcess and the XCTAssertTrue on
state.commands) to synchronize before asserting by either switching to
startMockServer and waiting on the serverHandled XCTestExpectation with
wait(for: [serverHandled], timeout: 5), or keep startMockServerAccepting and
call the waitForSocketCommand helper to wait until the expected "set_status
codex Idle" command appears in state.commands; reference
startMockServerAccepting, startMockServer, serverHandled, waitForSocketCommand
and the XCTAssertTrue that checks state.commands to locate where to add the
wait.
🪄 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: 18d4feed-e677-4278-b972-8e0ff9b64cc4

📥 Commits

Reviewing files that changed from the base of the PR and between bfefa88 and 78ce6a3.

📒 Files selected for processing (3)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • cmuxTests/WorkspaceRemoteConnectionTests.swift

Comment thread CLI/cmux.swift
Comment thread cmuxTests/WorkspaceRemoteConnectionTests.swift
Comment thread CLI/cmux.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread CLI/cmux.swift Outdated
@austinywang austinywang changed the title Fix agent running status after interrupt Clear agent Running status on interrupt signals May 19, 2026

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

♻️ Duplicate comments (1)
CLI/cmux.swift (1)

678-678: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat .needsInput as active for workspace-scoped checks.

Line 678 only counts .needsInput when surfaceId is present. For workspace-level callers, another session already in Needs Input will not block Idle, so a healthy completion can overwrite a live Needs Input state for the same workspace.

Suggested fix
-                    && (record.runtimeStatus == .running || (normalizedSurface != nil && record.runtimeStatus == .needsInput))
+                    && (record.runtimeStatus == .running || record.runtimeStatus == .needsInput)
🤖 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 `@CLI/cmux.swift` at line 678, The conditional that decides whether an existing
session blocks an Idle completion currently only treats record.runtimeStatus ==
.needsInput as active when normalizedSurface (surfaceId) is present; update the
check so .needsInput counts as active regardless of normalizedSurface for
workspace-scoped callers. Locate the conditional referencing
record.runtimeStatus, normalizedSurface and surfaceId in CLI/cmux.swift and
remove the normalizedSurface-dependent gating for .needsInput (keep .running
behavior the same) so a live Needs Input session blocks workspace-level Idle
replacements too.
🤖 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.

Duplicate comments:
In `@CLI/cmux.swift`:
- Line 678: The conditional that decides whether an existing session blocks an
Idle completion currently only treats record.runtimeStatus == .needsInput as
active when normalizedSurface (surfaceId) is present; update the check so
.needsInput counts as active regardless of normalizedSurface for
workspace-scoped callers. Locate the conditional referencing
record.runtimeStatus, normalizedSurface and surfaceId in CLI/cmux.swift and
remove the normalizedSurface-dependent gating for .needsInput (keep .running
behavior the same) so a live Needs Input session blocks workspace-level Idle
replacements too.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b0a9f7d-a01f-49f7-8102-a6fa7b3ec678

📥 Commits

Reviewing files that changed from the base of the PR and between a762689 and 972e0a4.

📒 Files selected for processing (1)
  • CLI/cmux.swift

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift
…atus-after-interrupt

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	CLI/FeedEventClassifier.swift
#	CLI/cmux.swift
#	Resources/Localizable.xcstrings
#	Resources/bin/cmux-claude-wrapper
#	cmuxTests/FeedEventClassificationTests.swift
#	tests/test_bash_integration_no_done_notifications.py
#	tests/test_claude_wrapper_hooks.py

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

Comment thread CLI/cmux.swift
now: now
)
state.sessions[normalized] = record
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nested turn clears idle early

Medium Severity

recordCodexMonitorIdleIfCurrent pops the completed turn from the prompt stack but always writes agentLifecycle and runtimeStatus as idle. When activePromptDepth stays above zero after that pop, the Codex monitor still succeeds and publishCodexMonitorIdle can clear panel lifecycle and status while an outer prompt turn remains active.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3eeee97. Configure here.

AsifAd pushed a commit to AsifAd/cmux that referenced this pull request Jul 17, 2026
…lish

Cherry-pick upstream interrupt/Codex-monitor work (stop-failure hook,
recordCodexMonitorIdleIfCurrent, surface-scoped running checks). Fix
stop-failure to clear Running to Idle instead of Completed.
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 3eeee97c Deployed Jul 8, 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.

Sidebar 'Running' status stays on after user interrupts an agent turn

3 participants