fix: detect Claude Code status transitions via capture-pane when hooks do not fire#69
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the Claude Code session monitor so status transitions remain accurate even when tmux hooks are missing, delayed, or unrecognized, by deriving state from tmux capture-pane output and by making monitor heartbeats sensitive to local rebuilds.
Changes:
- Add capture-pane-based Claude activity/prompt detection with debounced transitions to prevent
running↔idleflicker and incorrectwaitinghandling. - Fix interruption/API-error detection to inspect the last conversation JSONL line (ignoring trailing bookkeeping markers).
- Replace heartbeat “version” matching with a build ID (version + executable size + mtime) to restart stale monitors during local development.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| internal/monitor/monitor.go | Adds Claude capture-pane fallback logic (activity, permission prompt detection, debounced transitions) and improves JSONL tail interpretation; heartbeat now writes build ID. |
| internal/monitor/monitor_test.go | Adds extensive coverage for the new Claude status transition heuristics and prompt/activity matchers. |
| internal/monitor/ensure.go | Introduces monitorBuildID and uses it to decide whether a running monitor is “current”. |
| internal/monitor/ensure_test.go | Updates EnsureRunning tests to assert build-ID-based heartbeat matching. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+502
to
+505
| output, err := tmuxRunner.CapturePane(ctx, tmuxName) | ||
| if err != nil { | ||
| return nil // non-fatal | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude Code sessions could show a wrong status whenever the corresponding hook did not fire (or fired in an unrecognized form): a session stayed
runningafter Escape, bouncedwaiting → running → idleon cancellation, was briefly shownidleright after approving a permission prompt, flickeredrunning ↔ idlemid-turn, and wentidle → runninginstead ofidle → waitingwhen a permission prompt appeared.This change makes the monitor derive Claude Code status transitions from the terminal (tmux capture-pane) as a fallback:
running → idlewhen the agent was interrupted with no JSONL trace and no Stop hook (e.g. Escape right after submitting), requiring a sustained idle window so brief no-spinner gaps mid-turn do not flicker the status.running/idle → waitingwhen a permission prompt is visible, using a strict prompt-chrome matcher (footer hints and option lists) so assistant prose like "Would you like me to…?" is not mistaken for a prompt.waiting → idledirectly on cancellation andwaiting → runningimmediately on approval (terminal activity or a fresh transcript turn), never bouncing through the wrong state.last-prompt,file-history-snapshot) no longer mask them.↑/↓ N tokensreadout,esc to interrupt) across the whole pane, so a multi-line composer draft cannot hide them.The monitor heartbeat now uses a build ID (version + executable size + mtime) instead of the version alone, so a rebuilt binary restarts a stale monitor during local development.