Files
dots/common/.config/claude/StatusBar.md
T

7.8 KiB
Raw Blame History

Status line (statusline.py)

The custom Claude Code status line, ordered work info (left) → system info (right), segments joined by a light-gray │. It's responsive: always a single line — if the rendered width would exceed the terminal ($COLUMNS, which Claude Code re-exports on resize), trailing (lowest-priority) segments are dropped from the right until it fits.

Wide — everything:

📁 ~/.dots │ 🌿 .dots main* │ 💬 #42 3 · codex 2 │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 🔢 ↑63M ↓595k │ ⚡ 63 t/s │ ♻️ 91% 1h │ 💾 81%

Narrow — trailing segments trimmed:

📁 ~/.dots │ 🌿 .dots main* │ 💬 #42 3 · codex 2 │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m

Every segment is defensive: if its data is missing or a command fails, the segment is simply omitted (the bar never crashes the UI). Secondary detail (the parts in light gray) is supplementary to the main colored value.

Live segments

Segment Shows Notes
Vim mode [I]/[N]/[V] editor mode only with editorMode: vim; pair with hideVimModeIndicator: true so the built-in one doesn't duplicate it
📁 Directory Current working dir, with ~ for home cyan
🌿 Git repo branch + * if dirty + ↑N/↓N ahead/behind upstream repo = magenta; branch = green when clean, yellow + red * when dirty; ↑ cyan, ↓ yellow. Omitted outside a repo
💬 PR review threads #N of the current branch's open PR + its unresolved review threads (any author, outdated ones included; + past 100), Codex's share broken out as · codex N; green ✓ when none are left A thread counts for whoever opened it (its first comment's author; Codex = login matching codex, as in /pr-loop). PR-conversation comments are not counted — they have no resolved state, so a summary or an LGTM would never let it reach zero. A GitHub round trip is ~0.7 s, so a render only reads the per-branch cache ~/.claude/cache/statusline.pr.<hash>.json and, once it is older than 90 s, spawns a detached statusline.py --pr-refresh that updates it (one at a time, via a .lock claim) — new counts show on the next render. A failed fetch (no GitHub remote — e.g. this repo on gitea —, gh not authed, offline) backs off 10 min and keeps the last counts with a gray ?. Omitted with no gh, no open PR or a detached HEAD
🤖 Model Active model display name + · effort level (low/medium/high/xhigh) name = blue, effort = light gray; effort omitted for models without the param
📝 Context % of context window used + (Nk) tokens usage comes from the payload's context_window (used_percentage + current_usage, Claude Code 2.1.x); the transcript's last message.usage is only a fallback. Window from context_window.context_window_size, then (1M …) in the display name, else 200k — not from model.id, which arrives with any [1m] suffix already stripped. Adds a red ⚠compact at ≥80%
📊 5h usage % of the 5-hour rolling limit used + time until it resets from rate_limits.five_hour; Pro/Max only, and absent until the first API response of a session
💰 Cost $ session cost so far + $/h burn rate burn rate shown once the session exceeds ~30s
🔢 Session tokens ↑ input + ↓ output tokens used so far this session (cumulative, k/M) summed over the transcript's assistant message.usage blocks, de-duplicated by message id (one line per content block shares the same usage). Input = input + cache_creation + cache_read — everything billed as input, so on a 1M session it dwarfs output (every call re-reads the cached prefix). Main transcript only: subagent usage lives in separate sidechain files. Incremental: a per-session state file ~/.claude/cache/statusline.tokens.<session>.json remembers the byte offset already summed, so each refresh parses only the new tail (warm ≈ 0.2 ms), at most 8 MiB per refresh (≈ 150 ms) — Claude Code kills a status script when the next refresh fires, so a cold scan of a huge resumed transcript is spread over a few refreshes (totals climb, then settle) instead of restarting from zero each time. Stale state files are pruned after 7 days once 50+ pile up. Omitted when there is no transcript; ↑ cyan, ↓ green, no thresholds
⚡ Decode speed N t/s — tokens per second the model is generating duration-weighted mean over the last 8 thinking blocks of the main conversation: thinking_tokens / thinkingDurationMs from the same transcript scan (blocks under 1 s or 50 tokens skipped). The only clean per-call timing Claude Code records: the payload's cost.total_api_duration_ms also absorbs subagent and advisor calls (it grew 28 s in a 12 s wall interval with no main-conversation completion) and its output_tokens is sometimes a mid-stream partial, so neither can give a rate. Includes first-token latency, so short blocks read a little low and long ones converge on the real decode speed (Fable 5.1 measured 60–85 t/s). Needs thinking enabled — omitted until a thinking block has completed, never shown with thinking off
♻️ Prompt cache hit % (higher is better) + cache TTL (5m / 1h), yellow ·cold once it has expired from the payload's prompt_cache (2.1.260+: hit_ratio 0–1, ttl, warm); computed from usage on older payloads. A 1h TTL is what keeps a 10-min /pr-loop cadence warm
💾 Disk % used of the filesystem at the cwd from statvfs

Colour legend

Most numbers are green / yellow / red by threshold — green = healthy, red = needs attention. Light gray = secondary detail. Thresholds (green < … < yellow < … ≤ red):

Segment green yellow red
Context < 50% 50–80% ≥ 80% (⚠compact)
PR review threads ✓ none unresolved thread count codex N
5h usage < 50% 50–80% ≥ 80%
Decode speed (inverted) ≥ 40 t/s 20–40 t/s < 20 t/s
Prompt cache (inverted) ≥ 80% 50–80% < 50%
Disk < 75% 75–90% ≥ 90%

Light gray is \033[37m; dimming (\033[2m) is disabled because it blended the gray detail text into dark terminal backgrounds. To go brighter, set "gray" to \033[97m (bright white) in the C table near the top of statusline.py.

Optional segments (defined but not shown)

These functions exist in statusline.py but aren't in the output. Enable one by adding it to the work or system list in main():

RAM/CPU/temp were retired 2026-07-26: the status line only refreshes on message events, so system metrics sat visibly stale between turns — the tmux status bar (tmux/scripts/) owns live system metrics now.

Function Shows
🧠 ram_segment % used + used/totalG from /proc/meminfo (green <70% / yellow / red ≥85%)
🖥️ cpu_segment % busy (diffed /proc/stat snapshot) + loadavg + cores (green <60% / red ≥85%)
🌡️ temp_segment hottest CPU thermal zone °C (green <60 / red ≥80)
✏️ velocity_segment +added/-removed lines this session + lines/min
⚙️ api_segment share of wall-clock time spent in API/inference
version_segment Claude Code version (vX.Y.Z)
🎨 style_segment active output-style name

Where it lives

statusline.py is tracked in this repo and symlinked to ~/.claude/statusline.py; settings.json runs it via statusLine (/usr/bin/env python3 ~/.claude/statusline.py) on every message event and, through refreshInterval: 60, once a minute while the session idles — so the PR count moves while you wait on a review. Edits take effect on the next status refresh.