Read the status dots from Claude's own terminal title #23
No reviewers
Labels
No labels
bug
docs
enhancement
good first issue
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
pedromcaraujo/forge!23
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "status-dots-from-terminal-title"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #21, which was still not good enough.
The signal we were missing
Claude Code announces its state in the terminal title (OSC 0/2), and tmux
tracks it per pane as
#{pane_title}with no configuration at all:Verified live across every channel on nix-dev:
Why this beats what #21 shipped
The Esc case, measured live — hooks fire no
Stop, so #21 held the dot lituntil the 15-minute TTL. Now:
Changes
#{pane_title}added to the existinglist-sessionsformat; title is nowthe primary signal, hooks the fallback, transcript the last resort.
and is now redundant.
/statusis a single tmux call regardless of channelcount — previously one
capture-paneper channel per poll.sourcein the response is nowtitle|hook|transcript.Credit
Technique taken from herdr (Apache-2.0),
which uses the same OSC title as its highest-priority signal. Notably they
removed a hook-based design essentially identical to #21 — their changelog
still carries the uninstall list for
UserPromptSubmit/PreToolUse/Stop/SessionEndhooks — citing escape interrupts and permission results as thereason. No code taken, only the observation.
Test plan
nix flake check.hook fallback + TTL intact; transcript last resort; shell guard intact.
src=title, withno hooks installed (a concurrent
nixos-rebuild switchhad revertedthe test activation, which incidentally proves the zero-install claim).
✳-> braille for the whole turn ->✳exactly at the end.✳.Open question
With the title working this well and needing no install,
claudeHooks.enablemay not be worth its footprint — it writes a host-wide, highest-precedence
/etc/claude-code/managed-settings.jsonfor a fallback that may never fire.Happy to drop it if you prefer; left in for now.
Claude Code announces its state in the terminal title (OSC 0/2), and tmux tracks that per pane as #{pane_title} with no configuration: ⠂ Debug non-deterministic session detection <- a turn is running ✳ Claude Code <- waiting on the user Make that the primary signal. It beats everything tried so far: it comes from Claude itself rather than being inferred, it is per-pane rather than per-directory, it needs nothing installed so it is already correct on sessions that predate any deploy, and it lives in the byte stream — so unlike the rendered pane it is just as correct on a detached session. It also closes the hole hooks structurally cannot. Esc-interrupting a turn fires no Stop event, and neither does an API error; both return the title to the idle glyph at once. Measured on a live session, Esc now settles the dot in about 4s where the hook path held it lit until HOOK_BUSY_TTL — fifteen minutes. Hooks drop to being the fallback for anything that sets no title, still bounded by the TTL. The transcript mtime stays as a last resort. Drop the pane scrape entirely. It was wrong in both directions — the mark it keyed on had silently stopped matching anything, while still firing on any pane that merely *displayed* the phrase — and the title supersedes it. That also removes a capture-pane subprocess per channel per poll, so /status is now a single tmux call regardless of channel count. Credit for the technique: herdr (github.com/herdrdev/herdr, Apache-2.0), which uses the same OSC title as its highest-priority signal after finding hook-based lifecycle tracking insufficient for exactly these cases. No code taken.Hooks dropped
Removed the whole hook path: the script, the
managed-settings.jsonrendering, the
claudeHooksoption, the state directory, the TTL, and thehook-reading branch in
/status. Net −192 lines.Not a downgrade — the hooks were strictly the weaker signal:
/etcfile at highest precedenceWhat's left is one signal plus one fallback: Claude's terminal title, then a
transcript mtime for anything that sets no title.
Re-verified after removal — all six live channels, still correct:
nix flake checkpasses; classifier matrix re-run (title both ways, transcriptfallback, empty-title case, plain-shell guard).