Agent State in the Tmux Status Line A developer's attempt to render AI agent state (working, blocked, idle) directly into the tmux window list failed on both candidate signals: Claude Code sets its pane title once and never updates it, and tmux's pane_current_command reports versioned binary filenames such as 2.1.267 rather than 'claude', so the status-line rules for yellow and green never fired. The write-up, which also tested grok 1.0.30 (whose title does carry a braille spinner) and referenced the agent-first multiplexer Herdr and the tool agenmux, concludes the screen rather than OSC title updates is the viable source of agent state. The problem Running several agent sessions Claude Code, Codex, PingMe https://github.com/TheCloudlet/PingMe side by side in tmux windows makes it easy to lose track of which window is still working, which is stuck waiting on a decision, and which finished. Checking each window by hand doesn't scale past three or four of them. Herdr is the usual suggestion in this niche: an agent-first terminal multiplexer that classifies sessions as working, blocked, done, or idle and shows them in a sidebar. A week with it surfaced two objections — it's a separate surface to context-switch into for information that belongs next to the windows it describes, and it replaces tmux rather than fitting the existing habit of glancing at a status line. agenmux keeps tmux and answers the second objection, rendering agent state into a sidebar pane and a status-line segment. Both are regions separate from the window list. What follows writes the marker onto the window entries themselves. Herdr also tracks a fourth state, done — a turn that finished while the window was not visible, cleared on the next visit. That requires per-pane focus history, which the setup below does not keep; done collapses into idle, leaving three states. The target is the same classification, rendered in the tmux window list. Two signals looked like they carried it and did not. Attempt 1: the pane title tmux tracks pane title per pane, and programs update it via an OSC escape sequence. Agent CLIs are interactive TUIs, so the title is the first candidate for carrying their state. set -g @agent-status \ ' {? {m: Action Required , {pane title}}, fg=colour196 bold ,'\ ' {? {m/r: ^| ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒ |$ , {pane title}}, fg=colour220 ●,'\ ' {? {m/r: ^|/ codex|claude|pingme $, {pane current command}}, fg=colour34 ✓,}}}' Yellow never appeared once. Sampling the title twice a second through a full working turn explains why: bash $ for i in $ seq 1 40 ; do tmux list-panes -a -F ' {pane id} {pane title} '; sleep 0.5; done | sort -u %0 ✳ Tmux config article %1 ✳ Claude Code Two unique values across the whole sample. Claude Code sets its title once and never touches it again — it carries the session's name, not its state, and no regex over a constant string will produce a state machine. That sample contained two Claude panes. Grok, on the same tmux server: t=1 title= Review of tmux agent-status article - grok t=2 title= ⠋ - Waiting for response… - Review of tmux agent-status article - grok t=4 title= ⠋ - Thinking - Review of tmux agent-status article - grok t=7 title= ⠸ - Thinking - Review of tmux agent-status article - grok A braille spinner, in the title, from the character class the yellow rule matched on. The title is inert for Claude Code and not for grok, so a per-agent rule set could read grok's title directly. The screen carries state for both without depending on OSC title updates being emitted. Attempt 2: the foreground process With Claude's title ruled out, the next candidate is pane current command , the process tmux considers to be in the foreground. An agent running a tool occupies a different foreground process than one sitting at a prompt. bash $ tmux list-panes -a -F ' {pane id} cmd= {pane current command} ' %0 cmd= 2.1.267 %1 cmd= 2.1.273 Not claude . The install layout accounts for it: php $ ls -l ~/.local/bin/claude ... - ~/.local/share/claude/versions/2.1.273 $ ls -l ~/.grok/bin/grok ... - ../downloads/grok-1.0.30-macos-aarch64 Both launchers are symlinks to a versioned binary, and the process carries that binary's filename. tmux reports it: claude as 2.1.267 , grok as grok-1.0.30-mac , truncated from grok-1.0.30-macos-aarch64 . The value identifies a release artifact rather than the program. The green icon never lit on this machine either. Its rule required pane current command to end in claude , which this value never does, so the third branch fell through to the empty fallback alongside the other two. The process tree contains a process whose comm is claude , whatever the pane's foreground command is named, so ps -o comm= over the tree answers the presence question. State is a separate matter: pane current command identifies a process, and process identity does not encode "thinking" versus "waiting for approval." What actually carries the state agenmux https://github.com/snirt/agenmux , an open-source tmux agent monitor that ports Herdr's detection rules, documents its method: Detection is scraping-only: agents are identified by walking each pane's process tree, state is inferred from the pane's visible screen and title. The rendered terminal buffer, then, rather than the title or the process. Herdr matches its rules for Claude Code and Codex against a snapshot of the bottom of the live buffer. tmux exposes exactly that through capture-pane . The bottom of a working Claude Code pane: ✽ Brewing… 2m 14s · ↓ 3.9k tokens ───────────────────────────────────────────────────── ❯ ───────────────────────────────────────────────────── ⏵⏵ auto mode on shift+tab to cycle · esc to interrupt · ← for agents And the same pane once it's idle: ───────────────────────────────────────────────────── ❯ ───────────────────────────────────────────────────── ⏵⏵ auto mode on shift+tab to cycle · ← for agents esc to interrupt is present in one and absent in the other. The hint can only be rendered while something is interruptible, which makes it the state. The activity line above it carries a spinner as well ✽ Brewing… , through the glyphs ✳ ✽ ✶ — a different character class from the braille and circle set the first attempt searched for, on a different surface. The same config appeared to work on a Linux machine whose title was equally static. What lit there was green, which required only that pane current command resolve to claude — true on that box, false here. Green reported an agent process in the pane; it never reported what that process was doing. The implementation Two signals, each answering the question it can actually answer: the process tree for "which agent is here", the screen buffer for "what is it doing". The walk returns the agent's name rather than a yes/no, since the screen rules differ per agent: pane agent { root="$1"; pids="$root"; queue="$root" while -n "$queue" ; do pid="${queue%% }"; queue="${queue "$pid"}"; queue="${queue }" for c in $ pgrep -P "$pid" 2 /dev/null ; do case " $pids " in " $c " ;; pids="$pids $c"; queue="$queue $c" ;; esac done done ps -o comm= -p $pids 2 /dev/null | sed -nE 's|. /||; /^ claude|claude-code|codex|grok|pingme $/p' | head -n 1 } Then each agent gets its own patterns and its own order. Sketched as pseudocode — match stands in for a grep against the captured screen, and the patterns are elided; the appendix https://thecloudlet.github.io/technical/til/tmux-agent-status-indicator/ appendix-the-full-poller has the runnable version: claude, claude-code, pingme: working if screen matches 'esc to interrupt|ctrl+c to interrupt' idle if screen matches a bare '❯' prompt line action if screen matches 'do you want to proceed?|waiting for permission|…' idle otherwise codex: action if screen matches 'press enter to confirm or esc to cancel|…' working if screen matches 'esc to interrupt' idle otherwise grok: action if screen matches '