Quota, context, cost, model, and git -- live in your Claude Code prompt.
One Bash file that plugs into the official statusLine command hook. Shows
what matters: active model, context window, session cost, 5h / 7d quota with
reset times, prompt-cache health, extra-usage spend, git activity, and subscription tier.
When the numbers stop meaning what they appear to mean, the
notice engine interprets them — cap projections,
expiring-surplus and underuse advice, which model caps first, out-of-band
quota re-bases, sibling-account relief.
No daemon, no telemetry, no npm.
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh | bashDownloads statusline.sh to ~/.claude/ and wires up settings.json.
Restart Claude Code (or send a message) and the statusline appears.
Requires: Bash, jq, curl.
Paste this into Claude Code and it will set everything up:
Install claude-code-statusline: download https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/statusline.sh to ~/.claude/statusline.sh, make it executable, and add a statusLine command entry to ~/.claude/settings.json pointing to it with padding 0.
Manual install / inspect first
# Download and inspect
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh -o /tmp/install-statusline.sh
less /tmp/install-statusline.sh
bash /tmp/install-statusline.sh
# Or skip the installer entirely
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/statusline.sh -o ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.shThen add to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline.sh",
"padding": 0
}
}project (main*) +84/-14 1h30m $6.72 opus4.8[1m][███░░░26%] [MAX|you] 5h[87%@14:30] 7d[75%@2d] ex[$16.29/$200 8% bal$4.66]
| | | | | | | | | |
path branch edits time cost model+context user 5h quota 7d quota extra usage
Every component earns its place:
| Signal | Why it matters |
|---|---|
| Path and branch | Know where Claude Code is writing. Neutral grey; a dirty branch brightens to white with a *. |
| Trace chip | [http://localhost:9317/s/c3a6e0f3] when the session's wire is being captured by cctrace (deva --trace, or cctrace directly) — a full URL so terminals linkify it, deep-linking /s/<sid8> (cctrace >= 0.40) straight to this session's conversation scrolled to the newest turn; without a session id the link falls back to the /trace live page. DEVA_TRACE_UI_URL (exported by deva on traced create/reattach) outranks the container-side port, since only the host side knows the published port. Line 1 must fit the terminal (Claude Code hands the script COLUMNS and truncates or wraps anything wider, which throws every anchored row beneath the wrong edge), so when the full URL would not fit, the chip collapses to [cctrace] carrying the same target as an OSC 8 hyperlink — 9 columns, still one click on iTerm2/kitty/WezTerm — before the path/stats gap gives. Session identity, so it sits on the left with path and branch, dim (red stays reserved for pressure). Detected from the trace env cctrace exports into the traced process (CCTRACE_SERVER_PORT), from the capture's CA plumbing (NODE_EXTRA_CA_CERTS under a cctrace dir), or from DEVA_TRACE=1; when only the plumbing is visible (older cctrace) the port resolves through cctrace's live-instance registry, matched by session id (sid8 prefix — the registry stores ids redacted), then project path, then by being the only live capture, with the fallbacks trusting heartbeat-fresh entries only. A traced session with no resolvable port still shows a bare [cctrace] — "recorded" matters even portless. |
| Activity | Session diff without opening git. |
| Time and cost | Track long sessions. Hours format above 60m (1h30m). |
| Model | Abbreviated: claude-opus-4-8 becomes opus4.8, claude-fable-5 becomes fabl5, claude-sonnet-5 becomes sonnet5. The [1m] tag marks a 1M-context session, detected from the window the CLI reports (context_window_size) — not the name — so it shows even when Claude Code strips the [1m] suffix (which it does since 2.1.173 whenever 1M is the default; Sonnet 5 joined Fable 5 on that path in 2.1.197). |
| Effort | Compact lowercase badge: lo / md / xh / max / ultra / auto (high is the default and stays hidden). Dim for routine levels; max / ultra use the pressure color; fast shows in fast mode. |
| Context bar | Merged with model. Green / yellow / red by window pressure. On 1M models the bar also carries the premium input-pricing band: yellow past 200k tokens, red past 800k — the % alone looks calm (320k = 32%) while every request bills at the premium rate. A real 0% (e.g. right after /compact resets the window) renders a visibly empty bar [░░░░░░0%] — the snap to empty is the refresh signal; the bar hides only when no context data exists at all. Changes flash for ~60s in reverse video: [█░░░░░30%]+9 while filling, [░░░░░░3%]-27 right after a compaction. Cost flashes the same way, cents-precise: $7.90+.37. |
| User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. |
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — 5h[42%@14:30] reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge is hybrid: day-relative @5d while the reset is >= 24h out — decays one day per day, mild and narrow — switching to the same wall-clock @04:00 inside the last day, where an @6h/@<1h countdown decayed by the hour exactly when pressure keeps the suffix visible.) When a window's utilization climbs between renders, a reverse-video +N token appears right after the badge for ~60s: 5h[44%@14:32]+2 means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. 7d is forecast, not leveled: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows when relief arrives: 7d[44%@5d] red means "at your pace, dry days before the reset 5 days from now"; inside the last day it reads 7d[92%@04:00] — resets at 04:00. Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. Model-scoped weekly quota: when the usage API carries a per-model weekly limit (limits[], kind=weekly_scoped) for the model your session is running, it renders right after the model+context block — fabl5[1m][12%] fb[67%] on a Fable 5 session, op[33%] on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining this session is signal. Supersedes the legacy seven_day_opus/seven_day_sonnet fields, which the API now sends as null. |
| Extra usage | Monthly spend, limit, prepaid balance. --extra auto shows when quota runs out. |
| Week row | A row of its own, under the badges: 5h ▅█▃▮▯ 0.9✕ @23:00 7d ▅▁▂ ▃▅ˍ▃▅ …▮▯▯...▯5h✕19 0.7✕ @Wed 09:00 — this sitting by the hour, the week as its 5h windows (day-gapped, the far future folded to a counted ...▯5h✕19), height = what each cell burned, ▮ now, × where the pool runs dry, each strip ending with its pace and reset. Reconstructed from your own usage log; auto shows it once there is history to show. See Week row. |
| Deadman | Invisible until a switch is armed. Surfaces deadman — a dead man's switch that hands the session off when you stop responding. [☠ armed 42m] (dim) counts down to the auto-handoff; [☠ warned 3m] (yellow) means the phone warning went out; [☠ due] means the handoff fires imminently. Sits on the left lane next to the path — it describes this session's lifecycle, not a quota. One command -v when the tool is absent, one fast file read when present; nothing armed renders nothing. --deadman off disables it. |
| Cache health | Quiet until it bites. Claude Code never re-renders an idle session, and while you work the prompt cache is always freshly ~1 TTL from expiry — so a proactive "expiring soon" isn't honestly observable, and auto spends no width on it. It speaks only when a rewrite actually happens: ≡!419k the instant you resume onto a dead cache (idle longer than the TTL) or a mid-session prefix collapse — a 419k-token re-cache at ~20x the read rate (and the same burn on your 5h/7d quota on subscriptions). Bold red past 200k — the premium-band miss. ≡~ while a large prefix rebuilds. --cache always additionally keeps the freeze-safe deadline ≡@15:20 (last request + TTL; a past time in a frozen frame reads "expired at 15:20"). TTL defaults to 1h (claude.ai subscriber sessions) or 5m (API-key auth); an observed usage breakdown overrides it. The ≡ glyph (U+2261) reads as stacked cache layers — one terminal column, quiet and distinct. |
Color follows three lanes so a glance is unambiguous: status (green/yellow/red) = pressure only — quota, context, cache, the premium context band, expensive effort; identity (magenta/cyan/blue; fable = bright red, matching its Claude Code TUI color) = model family; everything else is neutral grey/white. Warm status color always means "near a limit or cost."
| Feature | /statusline |
This repo |
|---|---|---|
| Context bar, cost, git | Yes | Yes |
| Live 5h / 7d quota | -- | Yes |
| Extra usage + prepaid balance | -- | Yes |
| Quota reset time | -- | Yes |
| Adaptive polling (30s -- 5min) | -- | Yes |
Refresh ~ / error ! indicator |
-- | Yes |
--extra display gating |
-- | Yes |
| Tier display + model abbreviation | -- | Yes |
| 5 themes, 9 bar styles | -- | Yes |
| Prompt cache break detection | -- | Yes |
| OAuth + macOS Keychain | -- | Yes |
Change flash on every refresh (+.37 cost, +9/-27 context, +N quota) |
-- | Yes |
Model-scoped weekly quota (fb/op/sn) |
-- | Yes |
| Week row: the 5h window by the hour, the 7d period as 5h windows, what each cost | -- | Yes |
| Works behind trusted mitm proxies (NODE_EXTRA_CA_CERTS) | -- | Yes |
| 383 bats tests + CI | -- | Yes |
Flags go in the command string in ~/.claude/settings.json:
"command": "bash ~/.claude/statusline.sh --theme developer --extra on-limit"| Flag | Values | Default |
|---|---|---|
--theme |
minimal, compact, detailed, developer, manager |
(none) |
--style |
unicode-blocks, single-block, bracketed-bars, filled-dots, square-blocks, line-segments, ascii-bars, percent-only, fraction-display |
unicode-blocks |
--order |
Comma-separated: activity,time,cost,model,user,quota,extra |
all |
--path-display |
project, cwd, full, relative |
project |
--alignment |
left-right, right-left, center |
left-right |
--extra |
auto, always, on-limit, off |
auto |
--cache |
auto, always, off |
auto |
--advisor |
auto, always, off — the pinned notice, see The notice engine |
auto |
--week |
auto, always, off — the 5h + 7d ledger row with pace and reset, see Week row |
auto |
--notice |
auto, off — the fading row 3 (a new notice explained in full for ~90 s), see The notice engine. STATUSLINE_NOTICE sets it too |
auto |
--deadman |
auto, off — deadman switch chip |
auto |
--debug |
Write logs to ~/.claude/statusline/logs/statusline.log |
off |
--test [json] |
Render with mock data | off |
| Theme | What it does |
|---|---|
minimal |
Model + context + user. Extra off. |
compact |
Everything. Unicode bars. Project path. |
detailed |
Bracketed bars. Working directory. |
developer |
Full path. Filled dots. Right-aligned. Extra on-limit. |
manager |
Percent-only. Cost first. Centered. |
--extra auto 5h[24%@14:30] 7d[10%] (calm, hidden)
--extra auto 5h[87%@14:30] 7d[10%] ex[$19.52/$200 10%] (5h >= 80%, shown)
--extra always 5h[24%@14:30] 7d[10%] ex[$19.52/$200 10% bal$4.66] (always shown)
--extra on-limit 5h[87%@14:30] 7d[10%] ex[$19.52/$200 10%] (same as auto minus extra_util gate)
--extra off 5h[24%@14:30] 7d[10%] (always hidden)
auto (default) shows extra when quota runs out (5h >= 80%, 7d >= 70%) or
extra budget is pressured (utilization >= 50%). Compact by default, actionable
when it matters.
--cache auto fabl5[1m][42%] (healthy: silent — no width spent)
--cache auto fabl5[1m][5%] ≡~ (a large prefix is rebuilding)
--cache auto fabl5[1m][43%] ≡!419k (resume onto a dead cache: 419k rewrite; BOLD red >200k)
--cache always opus4.8[1m][21%] ≡:1h@14:20 (opt in to the freeze-safe deadline; :1h = observed TTL)
--cache off opus4.8[1m][21%] (disabled; no state writes)
Why healthy is silent. The server refreshes the cache TTL on every request,
so while you work the cache is always freshly ~1 TTL from expiry — there is no
honest "expiring soon" to show, and a deadline that's always ~an hour out is
pure width. The expiry only becomes real during an idle gap, and Claude Code
renders the statusline only on activity — so no warning can appear while you're
away. The honest moment is when you come back: the first post-idle render sees
activity resume after a gap longer than the TTL and reports the rewrite as
≡!Nk, sized at the re-cached prefix (≡!419k), bold red past
200k — the expensive premium-band miss. This fires even when the 300ms render
debounce skipped the read=0 turn, because the detector keys off the stale
activity anchor, not the one-frame collapse; breaks are held ~60s so a busy
turn's refresh doesn't erase them. A cache miss re-caches the whole prefix at
~20x the cache-read rate, and on subscription plans the same multiplier lands
on your 5h/7d quota.
The activity anchor is the last observed usage change, not the last render — Claude Code also re-runs the statusline on vim/permission/model changes with unchanged usage, and re-stamping there would fake a warm cache.
The freeze-safe deadline, on demand. --cache always adds ≡@15:20
(last request + TTL): a past time in a frozen frame reads "expired at 15:20",
so you can decide before typing whether to resume this session or start
fresh. Wall-clock, never a countdown — "expires in 43m" rendered an hour ago is
a lie, "@15:20" stays true however stale the frame is. Claude Code's statusline
stdin exposes aggregate cache tokens only, not the ephemeral_1h/5m breakdown,
so the TTL is assumed from how the CLI actually requests caching (verified in
traces): 1h for claude.ai subscriber sessions, 5m for API-key /
custom-endpoint auth (the CLI's FORCE_PROMPT_CACHING_5M /
ENABLE_PROMPT_CACHING_1H overrides are honored). An observed breakdown wins
and renders its class as provenance (≡:5m@14:25). Known gap: a subscriber
session that started while on overage is latched to 5m server-side, invisible
here — the ≡!Nk badge still reports the miss after the fact.
| 5h utilization | Interval |
|---|---|
| < 20% | 5 min |
| 20 -- 49% | 2 min |
| 50 -- 79% | 1 min |
| >= 80% | 30 sec |
Those intervals govern the API fetch. Claude Code itself hands the
statusline the 5h/7d numbers (rate_limits) on every render, and they are
merged into the badges immediately — so while stdin carries them the fetch
only serves what stdin lacks (the model-scoped weekly limit, extra usage)
and its interval floors at 2 min whatever the 5h heat. Every stdin pair
that changes is also logged as a source:"stdin" sample (>= 60 s apart):
free history for the ledgers and the forecast, no request behind it.
Error cooldown escalates with consecutive failures — 2 min, 4 min, 8 min, 10 min
cap — and a server Retry-After (429s carry one) extends it further. The
cooldown resets on the next successful fetch. Cache writes: atomic mv.
Indicators: ~ after quota = refresh in flight. A failed fetch shows why the
data may be stale: !429 rate limited, !auth token rejected, !5xx server
error, !net connection failed.
echo '{"model":{"id":"claude-opus-4-8[1m]","display_name":"Opus"},"cwd":"/tmp/project","workspace":{"current_dir":"/tmp/project"},"cost":{"total_cost_usd":6.72,"total_lines_added":84,"total_lines_removed":14,"total_api_duration_ms":5400000},"version":"2.1.139"}' \
| bash statusline.sh --testLine 1 says how much of each window is left; the week row says where it went — one grammar at two scales, directly under the badges. Row 2 mirrors line 1: the pinned notice on the left, evidence on the right, the gap between them absorbing the width, the right edge shared with line 1:
proj (main*) +84/-14 8m $6.72 fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%]
- 3✕5h left · 20%/win 5h ▅█▃▮▯ 0.9✕ 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7✕ @Wed 09:00
The 5h strip prints no reset: line 1's 5h[38%@23:00] already carries
it, and one badge per fact runs in both directions. The 7d badge shows a
reset only under pressure, so the 7d strip labels its own end until it
does.
The advisor sentence is compacted to the room line 1 leaves beside the
ledgers — weakest joint first (; the second voice, then · the tail
clause, then , a sub-fact), the leading fact last — so a calm frame is
two rows, not three. When fewer than 16 columns are left (narrow
terminals) the rows hang as a block instead: the ledgers meet line 1's
edge and the full sentence sits flush-left beneath them:
proj (main*) fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%]
5h ▅█▃▮▯ 0.9✕ @23:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7✕ @Wed 09:00
- budget ~3✕5h left · even 20%/win · heading ~52%
Each strip ends with its pace (used ÷ elapsed: 0.7✕ is on track,
1.6✕ caps early — dim below 1✕, pressure-tinted from 1✕, hidden for
the first 15 min of a window) and the reset its right edge stands
for (@23:00 inside 24h, @Wed 09:00 beyond) — axis labels for a
timeline, not badges restated. When the row shows, the advisor's calm
budget line shows with it (windows left, what even looks like, where
you land); pressure and surplus clauses still take its place when they
fire.
5h— this sitting: the current 5h window as 5 hour cells, height = the 5h points that hour added (each positive step between consecutive samples credited to the hour the later sample fell in).7d— the week: the 7d period as its 5h windows (34 slots, oldest left, the last a 3h stub), height = the 7d points that window burned, with a thin gap at each local midnight so days read as clusters — and a day that held five windows shows it — without a ruler. History draws in full; the future folds: two hollow cells after▮, then...▯5h✕28— 28 more 5h slots to the reset, all alike (×red when the tail projects dry). Theweeksubcommand's wide ledger still draws every slot.
| Cell | Meaning |
|---|---|
▁▂▃▄▅▆▇█ |
a cell that ran; height is the points it burned (▁ <= 2, ▅ <= 11, █ > 20) — the same scale in both strips, so a full window and a full week read the same height |
ˍ |
ran, cost under a point — or ran idle inside the log's coverage; a bar of height zero, on the baseline |
░ |
unknown: the log has no sample for that cell (never drawn as idle — a gap in the record is not a quiet session) |
▮ |
the cell you are in now |
▯ |
a cell still ahead of you — the hollow of ▮, an empty slot waiting |
× |
a cell the pool will not cover at the current pace (7d: the learned forecast's dry point, linear when untrained; 5h: linear, the same projection as the badge) |
...▯5h✕28 |
the folded 7d future: 28 more 5h slots to the reset, one token instead of 28 hollow cells (× red when the tail ends dry) |
✕ |
not a cell — the multiplication sign, the row's one operator (...▯5h✕28, 0.7✕, - 19✕5h left). Deliberately not × (U+00D7), which is already a reading: cells are the ink, the operator is punctuation, and ...×5h✕28 has to say both at once. One terminal column and no emoji fallback, so the row still meets line 1's edge; override with MULT_GLYPH (╳ and ✖ look stronger but are ambiguous-width and emoji-presentation respectively) |
Burn cells take their badge's pressure color; everything else is neutral,
so the row never adds an alarm channel of its own. Both strips are
reconstructed from usage.jsonl — the samples every render has been
logging — keyed by each 5h window's resets_at. Reading down the column:
7d[39%] -> the strip that spent those 39% -> the advisor clause that
projects the rest. Freeze-safe by construction: ▮ moves at cell
boundaries and every other cell is history.
--week auto (default) draws the row only once the log holds a sample
for either period — a fresh install gets no ░░░▮▯▯ row that says nothing
the badges don't. --week always draws it whenever a window is live;
--week off never. The 7d strip is the same one statusline.sh week
prints; both are cached in week.cache and rebuilt only when the log
grows, so a render never pays for the scan. Interoperates with
ccpace, which draws the same
week ledger from the same log.
Line 1 is the numbers; the notice engine is what they mean. Readers turn the live account into notices, each carrying a short form and a long one:
proj (main*) +84/-14 8m $6.72 fabl5[1m][██░░42%] fb[91%] [MAX|@work] 5h[38%@23:00] 7d[55%]
+ fb 91% vs 7d 55% · go op 5h ▅█▃▮▯ 0.9✕ 7d ▅▁▂ ▃▅ˍ▃▅ … ▆▆ˍ▂▮▯▯ 0.6✕ @Wed 09:00
+ fb weekly 91% against 7d 55% · the model caps first, not the account; op sits at 33%, so run it for the bulk
Row 2 pins the top notice, compacted to the room the ledgers leave — it stays while the condition holds. Row 3 flashes the same notice in full, but only for ~90 s after the condition first appears in this session: the explanation arrives once, then gets out of the way and leaves the pin. A second story that shows up later takes row 3 next. The number you act on is bold.
Three voices, one hue each:
- pressure —
! ...in yellow/red: a wall between here and a reset. - opportunity —
+ ...in cyan: capacity about to expire unused, a model with room, a free sibling account. Cyan can never mean pressure. - budget —
- ...in dim: the calm week in one breath.
One voice per window per frame (5h, 7d, fb, acct), so two
clauses about one window can never disagree.
| Notice | What it knows that line 1 doesn't |
|---|---|
! fb capped ~Thu 07:00 |
The weekly limit scoped to this session's model hit 100%: that model is gone until then. The long form names a model that still has room. |
! 5h caps ~05:18 |
Linear projection off this window's own pace: you hit the wall before the reset. Long form adds how long you'd sit blocked. |
! 7d dry ~Thu 09:00 · hard stop |
The learned per-weekday burn profile (EWMA over your own history), not a straight line — your heavy Tuesday counts more than an average. Tail says what happens at 100%: extra billing, or a stop. |
! fb 91% vs 7d 55% · go op |
The relation between two badges: the model caps before the account does. Switching models buys the week's remaining capacity back; the roomiest other weekly_scoped limit in the payload gets named. |
+ 7d rebased 53%→12% |
Utilization fell inside one window instance: burn never runs backwards, so this is a plan change or an out-of-band reset. The projections restart; the ledger still draws the old period. |
+ last 5h of the week · 47% unused |
The 7d window resets inside this 5h window: no later window exists to spend the remainder through. |
+ 47% unused · spend it (or · ~33% expires even at full burn) |
Expiring surplus, feasibility-checked against your learned pct_per_window: "spend it" only when full-tilt burn can actually consume it. |
+ 5h ~40m left · 70% unused |
Said only when the week is stranding capacity — an unspent 5h window is otherwise headroom, not waste, since the 5h window is a rate limit and not a budget. |
+ ~62% will expire · go heavier |
On pace to strand a large chunk of the subscription. Speaks only to an engaged, unsqueezed session. |
+ work 5h[8%] free |
A sibling account in the same shared home is idle while this one is pinned. |
- 19✕5h left · 1.1%/win |
The calm budget: runway, what even looks like, where you land (long form). |
--notice off keeps row 3 quiet; --advisor off silences both. --check
and --week print the long form, since a terminal command has a whole
line to spend.
The advisor prevents waste prospectively; report proves it
retroactively. It replays the usage log the statusline has been writing
all along and ledgers every closed window — what you used, what expired:
$ ~/.claude/statusline.sh report # or --days 90
usage report - work (last 28d, 79 samples)
7d windows closed: 1
Tue 07-28 00:00 used 51% expired 49% (~4.7 ✕ 5h windows unused)
5h windows closed: 3 avg 95% at close 2 hit the cap
exchange rate: one full 5h window = ~10.46% of the week (~9.6 windows/week, learned)
week in progress: 5% used, resets Mon 08-03 23:59That "expired 49%" line is the subscription math nobody shows you: half
a week of paid capacity, gone. The windows-worth figure uses the same
learned pct_per_window ratio the advisor's feasibility check uses, and
"week in progress" runs the same learned projection — the surfaces
cannot disagree.
Honest limits: a window's final utilization is the last sample before its reset, so usage from other devices after your last local render is invisible, and a week you never opened a session in never appears at all. The ledger reports what the log observed, nothing more.
The statusline never runs when you're away — exactly when expiring
capacity needs a voice. Instead of shipping a daemon, check exposes
the advisor's judgment as an exit code; you provide the plumbing (tmux
segment, cron, CI):
~/.claude/statusline.sh check
# stdout: the plain advisor text, or "calm" / "unknown: ..."
# exit 0 calm | 1 opportunity (+) | 2 pressure (!) | 3 unknown/stale# cron: nudge yourself when paid capacity is about to expire unused
*/30 * * * * ~/.claude/statusline.sh check; [ $? -eq 1 ] && notify-send "$(~/.claude/statusline.sh check)"
# tmux: advisor verdict in the status bar
set -g status-right '#(~/.claude/statusline.sh check)'session-summary is the same idea for session retrospectives — one
line per session, built from the usage log, designed as a SessionEnd
hook (it reads the hook JSON on stdin):
session 8f3c02aa: 3h12m, 5h +34pts, 7d +4pts, claude-fable-5
Run it bare and it summarizes the last session in the log. Window deltas are positive-delta sums, so a session that straddles a 5h reset still reports what it actually consumed.
Three layers, one source of truth: line 1 shows the numbers, line 2 says the one sentence that matters, and for the full conversation — "should I start a heavy task now?", "which account has headroom?", "what did I waste this week?" — there's a skill that teaches Claude Code itself to read the state dir:
cp -r skills/usage-insight ~/.claude/skills/Then just ask. The skill knows the state-dir contract
(docs/api/state-dir.md), the learned-forecast semantics
(pct_per_window, weekday profile, prediction calibration), and the
advisor's judgment rules — including the important one: never advise
what the data can't back. It reads the same files and runs the same
report/check subcommands, so all three layers always agree.
OAuth and API behavior
Quota, profile, and extra-usage requests use Claude Code's OAuth credentials
from ~/.claude/.credentials.json. Expired tokens are refreshed automatically
via the same refreshToken flow the CLI uses.
If ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or ANTHROPIC_BASE_URL is set,
all OAuth-dependent components (quota, user, extra) are skipped silently.
macOS Keychain is tried as fallback when no file credential exists. OrbStack
resolves the real Linux home directory via getent passwd.
The script never writes to Anthropic endpoints -- it only reads usage, profile, and prepaid balance data.
Account-scoped cache (one fetch, all sessions). Quota / profile / prepaid
data is identical for every session on the account, so it's cached once in a
shared dir -- ~/.claude/statusline/ -- and a single fetch serves all
concurrent sessions. Per-session prompt-cache-health state stays under
~/.claude/statusline/sessions/. Old $SCRIPT_DIR state is migrated on first
run. Setting only CLAUDE_CACHE_DIR keeps the legacy single-dir behavior.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
CLAUDE_CONTEXT_LIMIT |
auto | Override context token limit |
CLAUDE_7D_WORKDAYS |
unset | Skip weekends in the 7d pace deadline — quota you won't spend Sat/Sun no longer counts against runway (opt-in; the limit itself stays calendar-based) |
CLAUDE_DATA_DIR |
~/.claude/statusline |
Account-scoped cache + usage log location |
CLAUDE_CACHE_DIR |
$CLAUDE_DATA_DIR/sessions |
Per-session cache-health state |
STATUSLINE_DEADMAN |
auto |
Default for the deadman chip (auto/off); the --deadman flag wins |
STATUSLINE_ACCOUNT |
unset | Account label for multi-account setups: renders an @label chip and moves account caches to accounts/<label>/ so concurrent accounts stop sharing one quota cache |
DEVA_AUTH_TAG |
unset | Same as above, set automatically by deva from --auth-with (auth-file-<stem> -> @<stem>); auth-default means single-account and is ignored. Containers from pre-v0.18 deva without the tag are resolved from DEVA_AUTH_METHOD/DEVA_AUTH_DETAILS instead |
DEBUG_LOG |
~/.claude/statusline/logs/statusline.log |
Debug log path |
DEBUG_LOG_MAX_BYTES |
1048576 |
Debug log size cap before rotation |
CLAUDE_CODE_MAX_OUTPUT_TOKENS |
32000 |
Output token reserve |
CLAUDE_CONFIG_DIR |
~/.claude |
Claude config directory |
npm exec --yes bats -- t/418 tests across t/statusline.bats (406 statusline + integration) and
t/install.bats (12 installer). CI runs on push and PR to main.
statusline.sh Main script (one file, ~4400 lines)
DESIGN.md The language: rows, color lanes, glyphs, time, requests
llms.txt Agent-facing map of the repo
install.sh One-line installer
t/statusline.bats Unit and integration tests
t/install.bats Installer tests (mock curl, isolated $HOME)
t/helpers.bash Sources real functions from statusline.sh
.github/workflows/test.yml CI workflow
CHANGELOG.md Release notes
CONTRIBUTING.md Contribution guide
docs/devlog/ Implementation history
docs/api/oauth-usage.md Observed /api/oauth/usage contract (synced: CLI v2.1.201)
docs/api/state-dir.md On-disk state contract for external readers (ccpace, agents)
DESIGN.md: rows, lanes, glyphs, time, requests — the rules every badge follows, in one page.
See CONTRIBUTING.md. Short version: keep it to Bash + jq +
curl, add tests, run bats t/ before pushing.