Skip to content

fix: reliable idle/busy/waiting detection for Claude panes#15

Merged
corwinm merged 2 commits into
mainfrom
fix/claude-idle-state-detection
Jul 18, 2026
Merged

fix: reliable idle/busy/waiting detection for Claude panes#15
corwinm merged 2 commits into
mainfrom
fix/claude-idle-state-detection

Conversation

@corwinm

@corwinm corwinm commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Problem

Claude panes were showing the wrong status:

  1. Idle sessions showed the waiting ! icon (original report).
  2. After the first fix, idle sessions showed running instead of idle.

Root cause

Status came primarily from Claude Code hook events, which are unreliable for the busy→idle transition:

  • The Stop handler used a prose heuristic and flagged end-of-turn messages ending in ? as waiting.
  • Claude does not always emit a Stop after a tool batch, so the state got stuck on the last running-producing event (e.g. PostToolBatch).
  • Panes with no matching hook state fell back to a process detector that always returned running.

Fix

Make the live tmux pane preview the authoritative real-time signal for Claude activity:

  • esc to interrupt in the footer (or an active spinner status line) → running
  • an on-screen permission / multiple-choice prompt, or an interactive slash-command dialog (Enter to confirm / Esc to cancel) → waiting
  • any other readable Claude screen (no esc to interrupt) → idle

Hook state is now used to:

  • enrich session/title metadata, and
  • assert a structured wait the preview can't render (e.g. MCP elicitation) only within a 60s freshness window, which prevents stale waiting/running events from lingering.

Stop events are also classified as idle at the source, and capture failures still fall back to hook state → process detection.

Verified live

All local Claude panes now report correctly: idle sessions show idle, an actively-generating pane shows running, and a pane with the /effort menu open shows waiting — all via claude-preview.

Testing

  • Added regression tests: Stop stays idle; stale running hook state is overridden to idle by the preview; interrupt footer → running; slash-command dialog → waiting.
  • npm test (132 pass), npm run typecheck, npm run lint all clean.

corwinm added 2 commits July 17, 2026 15:19
A Stop hook event means Claude finished its turn and handed control back
to the user, which is an idle state. Previously the Stop handler ran a
prose heuristic over the last assistant message and flagged turns ending
in a question (or phrases like "would you like"/"should I") as
waiting-input/waiting-question, so idle panes showed the waiting "!"
icon.

Genuine blocking prompts still arrive via dedicated structured hooks
(PreToolUse+AskUserQuestion, PermissionRequest, Elicitation), so the
end-of-turn text heuristic is removed. The preview fallback is likewise
tightened to only infer waiting from a real multiple-choice list or
permission prompt rather than a trailing question mark.
Hook state alone is unreliable for the running->idle transition because
Claude does not always emit a Stop event after a tool batch, and panes
with no matching hook state fell back to a detector that always reported
'running'. Both paths made idle Claude panes render as busy.

The tmux pane preview is the real-time source of truth:
- 'esc to interrupt' (or an active spinner status line) => running
- an on-screen permission/multiple-choice prompt, or an interactive
  slash-command dialog (Enter to confirm / Esc to cancel) => waiting
- any other readable Claude screen => idle (absence of 'esc to
  interrupt' means Claude is not working)

Hook state is now used to enrich session/title metadata and, only within
a short freshness window, to assert a structured wait (e.g. MCP
elicitation) that the preview cannot render. Capture failures still fall
back to hook state and then process detection.
@corwinm corwinm changed the title fix: treat idle Claude turns as idle instead of waiting fix: reliable idle/busy/waiting detection for Claude panes Jul 17, 2026
@corwinm

corwinm commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7909ecea13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/claude.ts
return false;
}

return Date.now() - (state.updatedAt ?? 0) < CLAUDE_HOOK_WAIT_FRESHNESS_MS;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep active Claude wait hooks until they resolve

When an AskUserQuestion or Elicitation prompt stays unanswered for more than 60 seconds and the live pane preview does not expose that structured form, this freshness check makes attachRuntimeWithClaude stop honoring the hook and fall through to the preview's idle classification. Those hook events represent Claude being blocked until a follow-up response/result hook arrives, so the waiting indicator can disappear while Claude is still waiting on the user.

Useful? React with 👍 / 👎.

@corwinm
corwinm merged commit 1c35cea into main Jul 18, 2026
1 check passed
@corwinm
corwinm deleted the fix/claude-idle-state-detection branch July 18, 2026 02:13
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.

1 participant