Skip to content

Display Requirements

  • Date: 2026-08-02
  • Status: Approved (2026-08-02, ADR-0009)
  • Supersedes: the model segment of the current ~/.claude/statusline.js

Objective

The statusline renders a single, bounded, deterministic line whose primary element is the resolved provider/model actually used by the last assistant response (per ADR-0005/0006/0007), degrading gracefully to the requested model when no resolved route exists yet. It never displays a value it cannot source.

General rules

  • One line only. Output is a single string written to stdout; it never wraps.
  • Deterministic: identical inputs produce identical output (no timestamps beyond the staleness decision, no randomness).
  • Missing fields are omitted, never guessed (baseline behavior preserved).
  • Any total failure renders an empty string (Claude Code then shows its own default).

Line structure (segment order)

🤖 <route> | <bar + %> | 🪟 <used/window> | ⇡in⇣out | 🌿 <branch>/🌳 <worktree-name> <state>
  1. Route segment (new — replaces the old model segment; defined below).
  2. Context bar (new): a 10-segment ▓░ gauge plus the rounded used_percentage (<n>%); filled blocks and the % are health-colored by the same usage thresholds as before, empty blocks a dark grey. Omitted when used_percentage is absent.
  3. Fill meter (new): tokens-in-window over the window size (78K/200K); the numerator is derived from used_percentage so the bar, %, and meter always agree. Omitted when used_percentage or the window is absent.
  4. Token deltas (preserved): ⇡in⇣out from context_window.current_usage; omitted when absent.
  5. Git branch: 🌿 <branch> (or 🌳 <worktree-name> inside a linked worktree) from git status --porcelain -b against cwd; omitted when not a repo. In a linked worktree the branch label is replaced by the worktree's own name (basename of its per-worktree gitdir), so the 🌳 marker identifies the isolated checkout. Muted green on feature branches and bright red on a protected default branch — the static MAIN_BRANCHES list (main/master) unioned with each configured remote's HEAD default. A clean default branch in the main tree omits the segment entirely (it is not a noteworthy state); it still renders for any dirty, in-progress, detached, or worktree state. A state emoji follows the branch, drawn from git status --porcelain plus per-worktree operation markers:
State Emoji Detected by
Clean no changes
Modified ✏️ unstaged (index column Y)
Staged 📦 staged (column X), no unstaged
Untracked 🆕 ?? entries only
Mixed 🔄 staged and unstaged
Merge conflict ⚔️ U in either column, or AA/DD
Rebasing 🧩 rebase-merge / rebase-apply dir in the gitdir
Merging 🤝 MERGE_HEAD present
Cherry-picking 🍒 CHERRY_PICK_HEAD present
Reverting ↩️ REVERT_HEAD present
Detached HEAD 📍 branch header HEAD (no branch)

Precedence (highest wins): conflict → rebasing → merging → cherry-picking → reverting → detached → mixed → staged → modified → untracked → clean.

Segments are joined with | (space-pipe-space). Color is a presentation-layer tunable (lib/config.js STYLE), applied only at the render layer.

Route segment definition

Condition Render
Resolved route known (last assistant message has a model) provider/model — e.g., opencode/big-pickle
Resolved route known, provider unknown (mapping miss) model only — e.g., big-pickle
No resolved route yet (no assistant message in transcript) requested label verbatim — e.g., auto/cheap
Resolved route older than the staleness threshold append [stale] to the route — e.g., opencode/big-pickle [stale]

Notes:

  • provider/model uses the naming rules in 03-provider-model-naming.md. Model strings are displayed verbatim, never parsed or rewritten.
  • When the route is stale, the last known value is still shown (with the marker) rather than cleared; this is the defined fallback in Architecture.md.
  • The requested label (model.display_name || model.id) is a fallback only; once the first assistant message exists, the resolved route replaces it.

Staleness

  • Threshold: 5 minutes by default, a single configurable constant.
  • Definition: the resolved route is stale when the wall-clock time minus the last assistant message's timestamp exceeds the threshold.
  • Rationale: during active work, assistant messages arrive frequently and the route is fresh; 5 minutes avoids flagging normal reading pauses while still marking long idle periods where the next request would re-route. Because auto/cheap re-resolves on every request (ADR-0007), the marker is a soft signal, not a guarantee of what the next request will use.

Truncation

  • The route segment is bounded to 32 characters. When provider/model exceeds that, the model portion is truncated to fit while keeping the provider prefix, with a trailing :
  • opencode/very-long-model-identifieropencode/very-long-model-identi…
  • When the provider is absent, the model alone is truncated at 32 chars.
  • The decorative 🤖 prefix is applied at the render layer and excluded from the truncation bound (it is a fixed label, not route content).
  • The remaining segments are never truncated; the full line's length is therefore bounded by the sum of the segments (Phase 5 validates behavior at different terminal widths).

Non-functional constraints (carried from Architecture.md)

  • Read completes well within the documented 300 ms statusline debounce (transcript tail is sub-ms, verified).
  • Read-only; no writes; no live traffic; no network.
  • Never renders tokens, prompts, or request content (05-security-privacy.md).