diff --git a/CLI/cmux.swift b/CLI/cmux.swift index a9ecd374f7ea..6a40e8123810 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -28512,8 +28512,11 @@ struct CMUXCLI { // the ~60s-later idle_prompt Notification can consult it, and forwarded // to the app so it can suppress the done-ping until work truly drains. let hasPendingBackgroundWork = hasActiveClaudeBackgroundWork(parsedInput) - let hasUnsettledWork = stopFailure == nil && (hasPendingBackgroundWork - || parsedInput.rawObject?["stop_hook_active"] as? Bool == true) + // Claude sets stop_hook_active on a re-entry after a Stop hook + // blocked once. That flag describes hook recursion, not work + // still running; only authoritative background-work signals + // should keep the sidebar in Running. + let hasUnsettledWork = stopFailure == nil && hasPendingBackgroundWork // Update session with transcript summary and send completion notification. let completion = stopFailure.map(claudeStopFailureSummary) ?? summarizeClaudeHookStop( diff --git a/tests/test_claude_hook_stop_last_assistant.py b/tests/test_claude_hook_stop_last_assistant.py index 2838273f5882..c1f6e2fbfb8e 100644 --- a/tests/test_claude_hook_stop_last_assistant.py +++ b/tests/test_claude_hook_stop_last_assistant.py @@ -155,12 +155,13 @@ def main() -> int: payload = { "session_id": f"sess-{uuid.uuid4().hex}", "hook_event_name": "Stop", + "stop_hook_active": True, "cwd": "/Users/lawrence/fun", "last_assistant_message": "2", } with CapturingSocketServer(workspace_id=workspace_id, surface_id=surface_id) as server: - env = os.environ.copy() + env = {key: value for key, value in os.environ.items() if not key.startswith("CMUX_")} env["CMUX_SOCKET_PATH"] = server.socket_path env["CMUX_WORKSPACE_ID"] = workspace_id env["CMUX_SURFACE_ID"] = surface_id @@ -262,6 +263,14 @@ def main() -> int: if notifications[0] != expected: print(f"FAIL: incorrect semantic completion: {notifications[0]!r}") return 1 + if not any( + command.startswith("set_status claude_code Idle ") + and f"--tab={workspace_id}" in command + and f"--panel={surface_id}" in command + for command in server.commands + ): + print(f"FAIL: final Stop with stop_hook_active=true did not settle Idle: {server.commands!r}") + return 1 print("PASS: Claude cron guard denies durable jobs and Stop notification uses final assistant text") return 0