A transcript ended with an assistant message. Is Claude Code finished?
Not necessarily.
That line may be the end of the current turn. It may belong to an old session. Claude Desktop may have written bookkeeping metadata after it. The user may already have replied. Or the assistant message may actually be an API error carrying a stop reason that looks deceptively final.
This is the awkward part of building a local status monitor: parsing JSONL is easy. Deciding what the evidence means right now is the engineering work.
File activity is evidence, not state
Claude Code and Claude Desktop already leave useful records on the machine. A local companion can inspect them without shipping transcripts to a hosted service. That gives us a strong privacy boundary, but it also removes the convenience of asking a server for a single authoritative status.
The first implementation temptation is to map modification time directly to state:
modified recently -> working
not modified -> idle
It fails in both directions. A session may be working while writes arrive in bursts. A metadata write may touch a file after the actual turn ended. A quiet file may mean the model is thinking, waiting on a tool, stalled, or simply abandoned.
The timestamp is useful, but it cannot carry the whole decision.
Completion has to survive newer noise
For Claude, a terminal assistant event is one input to the your-turn decision. The scanner also tracks the event identity and semantic timestamp. That matters because Claude Desktop can update its activity record a few seconds after the final assistant event.
If every newer desktop timestamp meant "the user came back," that bookkeeping write would erase the completion state before the alert fired. We give that short post-completion write a grace window. Activity well after the grace is different: it is evidence that the user probably returned, so the old completion should be suppressed.
This distinction came directly from observed event ordering. It is not something a generic file watcher can infer.
A new turn cancels the old answer
The parser works from the latest meaningful main-thread event, not merely the last physical line. A user event after an assistant completion means the old handoff is over. A streaming assistant event without a terminal stop remains working.
Sidechain events need separate treatment. Tool and subagent traffic can appear after the main assistant event. Letting a sidechain completion finish the main turn creates noisy alerts; letting trailing sidechain lines hide a real main-thread completion loses alerts. The parser therefore identifies the latest relevant main-thread event first.
API errors are another trap. A rate-limit response may be represented as an assistant line with a stop reason. That is attention, but it is not a successful your-turn completion. The error marker must win.
State needs an expiry policy
Even a correctly detected completion is not fresh forever.
Our scanner keeps bounded windows for working, your turn, stalled, and attention. A session previously observed as working can become stalled after sustained silence. A completed turn eventually ages back to idle. Evidence outside the attention window is ignored.
The exact thresholds are implementation details that may change. The invariant is more important:
old evidence must not create a new interruption
That rule also applies at app launch. Existing completed turns become baseline history. We do not pop an alarm for every session that finished before the monitor started.
Multiple files still have to become one signal
A real machine can have duplicate Claude Desktop records, archived threads, main transcripts, and subagent transcripts. A status bar cannot render that raw inventory.
The monitor filters archived and machine-driven sessions, deduplicates repeated session IDs, and then chooses the most urgent current state for the provider. Subagent alarms remain opt-in because orchestration can finish many child turns in a burst while the human only cares about the parent workflow.
This is less impressive than claiming "real-time AI observability," but it is more useful. The user needs one defensible answer: working, your turn, or attention required.
Tests should encode event order, not just fixtures
The regression cases that matter are sequences:
- assistant completion, then a user reply;
- completion, then a short bookkeeping write;
- completion, then genuine later desktop activity;
- sidechain completion after unfinished main output;
- API-error assistant line with a stop reason;
- old semantic activity with a freshly touched file.
Each sequence protects a state transition. A parser test that only checks whether one JSON object can be decoded would miss the actual failures.
The boundary
A local monitor can tell us that Claude is producing output, ended a turn, or appears to need attention. It cannot prove the requested task is correct or that generated code is safe. "Your turn" means a reply is needed, not that the engineering work is done.
Agent Island uses this local model for its Claude Code and Codex status surface. The longer engineering guide, including the freshness boundaries and released scope, is here: Claude Code status monitor.
The project is open source: inspect the scanner and tests on GitHub.
Top comments (0)