[FEATURE] Terminal title has no "waiting for you" state — the three-state tab-status path already ships, gated off by a hardcoded return false

Status Open
Reported on v2.1.236
Maintainer reply None cached
Activity 0 comments · opened Aug 25, 2026

Preflight

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

The terminal title carries a two-state vocabulary: busy or not busy. There is no
state for "I have stopped, and I am waiting on you."

That is the only state that matters when the window is not focused — which is
precisely when the tab is the only UI you have. A permission prompt, a plan
approval, a finished turn: all of them leave the tab looking exactly like an idle
session that has been sitting there for an hour.

This is not a request to build a state indicator from scratch. The three-state
indicator already exists in the shipped binary, fully written, and is switched off
by a hardcoded return false.
The ask is to reach it.

What ships today (2.1.243, native install, verified)

The title component is small enough to state exactly:

uRs = ["◐", "◑"];  // ◐ ◑ busy frames
dRs = "✳";              // ✳ idle glyph
GXg = 960;                   // ms

staticUnderMux = getMux() !== null && gate("tengu_static_title_under_mux", true);
useInterval(tick, disabled || noPrefix || staticUnderMux || !isAnimating || !focused ? null : GXg);
prefix = (isAnimating && !staticUnderMux) ? uRs[idx] ?? dRs : dRs;
useTerminalTitle(disabled ? null : noPrefix ? title : `${prefix} ${title}`);

useTerminalTitle sets process.title, which lands as OSC 0. Windows Terminal
renders it as the tab title, so the tab does visibly animate. Anyone can confirm
the cadence from a child process sharing the console, without instrumenting
anything:

# run while a turn is in flight
$sw=[Diagnostics.Stopwatch]::StartNew(); $prev=$null
while($sw.Elapsed.TotalSeconds -lt 6){
  $t=[Console]::Title
  if($t -ne $prev){ "{0,6} ms  {1}" -f [int]$sw.Elapsed.TotalMilliseconds,$t; $prev=$t }
  Start-Sleep -Milliseconds 20
}
     2 ms  ◑ Windows Terminal thinking animation
   519 ms  ◐ Windows Terminal thinking animation
  1478 ms  ◑ Windows Terminal thinking animation
  2456 ms  ◐ Windows Terminal thinking animation
  3417 ms  ◑ Windows Terminal thinking animation

Two frames, one glyph slot, one boolean behind it. Busy or idle. Nothing else can
be expressed.

The three-state path already exists — and is unreachable

Alongside the title component, the same build carries a complete tab-status
implementation with an explicit waiting state:

wkS = {
  idle:    { indicator: rgb(0,215,95),   status: "Idle",     statusColor: rgb(136,136,136) },
  busy:    { indicator: rgb(255,149,0),  status: "Working…", statusColor: rgb(255,149,0)   },
  waiting: { indicator: rgb(95,135,255), status: "Waiting",  statusColor: rgb(95,135,255)  },
};

useTabStatus(state, statusText) serialises that to an OSC TAB_STATUS sequence —
indicator=#rrggbb;status=<text>;status-color=#rrggbb, with ; and \ escaped in
the status text — and emits it on state change, clearing it on unmount. It is
gated on a capability probe:

function WKn(){ return !1 }

Hardcoded false. The emitter, the palette, the escaping, the cleanup path and the
waiting state are all compiled in, and none of it can run.

Two further details suggest this was built for exactly the case in this report and
then parked:

  • The terminal capability set in the same module already lists windows-terminal

alongside ghostty, kitty, WezTerm, konsole, mintty and the rest.

  • A showStatusInTerminalTab key already exists in the display settings group

(next to terminalProgressBarEnabled, showTurnDuration, showMessageTimestamps),
so the user-facing switch is present too — but per #88360 it is reachable only in
combination with the internal tengu_terminal_sidebar gate.

So the feature is not missing. It is finished and disconnected.

Proposed Solution

Three tiers, independently shippable, cheapest first.

1. A third glyph in the slot that already exists (no new protocol)

Give waiting its own character in the prefix slot the title already writes:

| state | glyph | meaning |
|---|---|---|
| busy | / | working, as today |
| waiting | (or any distinct mark) | stopped, needs a human |
| idle | | as today |

This requires no capability detection, no terminal allowlist and no new escape
sequence. It works in Windows Terminal, plain tmux, screen, mosh, a serial
console and everything in between, because it is just a character in a string that
is already being written. It is the lowest-common-denominator half of #71369, and
it alone would resolve the reported problem.

2. Make the existing tab-status path reachable

Replace the hardcoded false with a real capability check, and put it behind the
showStatusInTerminalTab key that already exists, without requiring an internal
gate. Terminals that don't understand the sequence ignore an unknown OSC, so the
blast radius of a wrong allowlist entry is a no-op rather than garbage on screen.

This is where the colour and the status text (Working… / Waiting / Idle) earn
their keep, and it is already written.

3. Map the states onto OSC 9;4 for terminals that support it

terminalProgressBarEnabled already exists as a display setting. Windows Terminal
and ConEmu render OSC 9;4 on the tab and on the taskbar button, with states that
map onto this problem almost exactly:

| Claude state | OSC 9;4 state |
|---|---|
| busy | 3 — indeterminate (pulsing) |
| waiting on you | 4 — warning (yellow) |
| idle | 0 — cleared |

The taskbar is the only surface that still works when the window is minimised,
which no title-based approach can reach.

One behaviour worth reconsidering regardless

The frame timer is disabled when the terminal loses focus:

useInterval(tick, ... || !focused ? null : 960);

For the busy state that is defensible — don't repaint a tab nobody is looking at.
For a waiting state it inverts the purpose: the session goes quiet and asks for
you at the exact moment you have tabbed away, and that is the moment the indicator
stops moving. Whatever form the waiting signal takes, it should not be the one
suppressed by blur.

Related issues (searched)

The tracker has approached the title from several directions; none report that the
three-state implementation exists and is gated off.

  • #71369 — closest: asks for unread/active/idle state in the tab title for

terminal/tmux users, proposing prefix characters with colour as an enhancement.
That request is for the design; this one supplies the finding that Anthropic
has already built it — palette, sequence, escaping and a waiting state included —
and needs only to reach it. Tier 1 below is that request, in its minimal form:
#71369 is best treated as a sub-request of this one, and closing it out falls
straight out of shipping tier 1.

  • #88360 — the 960 ms rewrite churn under tmux -CC, and the observation that

the no-glyph path is reachable only behind tengu_terminal_sidebar +
showStatusInTerminalTab. Same component, opposite complaint: too much writing,
not too little meaning.

  • #88542 — since 2.1.236 tengu_static_title_under_mux pins the glyph to

under any multiplexer, making busy indistinguishable from idle for tmux users.
Note that tier 1 above degrades under that gate too, and tiers 2/3 would not.

  • #76092 — a setting to disable or customise the title; adjacent (control over

the string) rather than overlapping (meaning carried by the string).

  • #84789CLAUDE_CODE_DISABLE_TERMINAL_TITLE ignored for background sessions.
  • #79617 — presence-aware notification control; the notification-channel

analogue of this problem. Notifications are interruptive and OS-gated; a tab
glyph is ambient and always available. They are complements, not substitutes.

  • #13024 (closed) — a hook for "waiting for user input". A hook would let users

build tier 1 themselves; it is not shipped, and a built-in state costs less than
every user reimplementing it.

  • #41484 (closed) — Ghostty OSC 9/99 notification support, cited as precedent

that terminal-native signalling is in scope.

Costs, acknowledged

  • Tier 1 is one extra glyph constant and one more branch on an existing

ternary. The title is already written on state change; nothing new is emitted.

  • Tier 2 is a capability check plus a settings key that already exists. Unknown

OSC sequences are ignored by conforming terminals, so a wrong allowlist entry
fails closed.

  • Tier 3 interacts with #88360's churn concern — OSC 9;4 should be emitted on

state transition, not per animation frame.

  • Glyph choice is bikeshed-prone. Any distinct character works; the value is in

the state existing at all, not in which mark represents it.

Environment

  • Claude Code 2.1.243, native install (~/.local/bin/claude.exe)
  • Windows 11 Pro 26200.9168, Windows Terminal, PowerShell 7
  • Reproduced outside any multiplexer, so tengu_static_title_under_mux is not in play

View original on GitHub ↗