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 and the usage-insight skill to ~/.claude/
and wires up settings.json. Re-run it any time to update: it replaces
the script but keeps the flags already on your statusLine.command.
Restart Claude Code (or send a message) and the statusline appears.
Requires: Bash, jq, curl. STATUSLINE_SKILL=0 skips the skill;
CLAUDE_CONFIG_DIR targets another config dir.
git clone https://github.com/thevibeworks/claude-code-statusline
cd claude-code-statusline
make install # this tree -> ~/.claude, same installer, no download
make status # installed vs tree, the settings command, the skillmake install runs install.sh with STATUSLINE_SRC set, so the local
path and the curl one-liner share one installer and cannot drift. It
refuses to install a statusline.sh that does not parse. make install-check runs shellcheck and the test suite first; make help
lists the rest.
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-1 becomes fabl5.1 (claude-fable-5 stays 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), and it drops when you turn 1M off (CLAUDE_CODE_DISABLE_1M_CONTEXT=1 makes the CLI report 200k; the bar re-bases to 200k and the tag follows). |
| 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 sessions the bar also carries rewrite exposure โ what a cold cache re-bills: yellow past 200k tokens, red past 800k โ the % alone looks calm (320k = 32%) while a dead cache rewrites all of it at the write rate. Long context itself is standard-priced on every 4.6+ model, so the color is about the rewrite, never a per-token premium. Sized from the CLI's prompt_cache.recache_tokens_if_cold on Claude Code 2.1.251+, else the live context. 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 โ
โโ โโ
โโโ
โฆโฎโฏโฏ...โฏ(โ19) 0.7โ @Wed 09:00 โ this sitting hour by hour on a fixed five-slot axis (the hollow run is the hours left), the week as its 5h windows (day-gapped, the far future folded to a counted ...โฏ(โ19)), height = what each cell burned, โฎ = now. ร marks where the pool runs dry on the week; each strip ends 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 the write rate (20x a read on most models, 80x on Fable 5.1 where reads cost 0.025x), and a burn on your 5h/7d quota on subscriptions. Bold red past 200k โ the heavy rewrite. โก~ while a large prefix rebuilds. On Claude Code 2.1.251+ the badge reads the CLI's own prompt_cache ledger: a miss is what the CLI counted as one, a compaction rebuild shows โก~ rather than โก!, and the TTL and deadline are exact; older CLIs keep the inferred detector. --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"). Without the ledger, 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 rewrite exposure past 200k, 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 heavy rewrite. On Claude Code 2.1.251+ the CLI sends its own
prompt_cache ledger (misses, expected rebuilds, tokens re-cached, TTL,
expiry) computed from every response, including the turns the 300ms render
debounce skipped โ when present it drives the badge: a miss is what the CLI
counted, a compaction rebuild shows โก~ instead of โก!, and the deadline is
exact. Older CLIs keep the inferred detector, which keys off the stale
activity anchor rather than the one-frame collapse. Either way breaks are held
~60s so a busy turn's refresh doesn't erase them. A cache miss re-caches the
whole prefix at the write rate โ 20x a cache read on most models, 80x on
Fable 5.1 where reads cost 0.025x โ and on subscription plans the rewrite
burns 5h/7d quota too.
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 ยท lands ~52% 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 ยท lands ~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 until
5% of the window has run: 15 min for 5h, ~8h for the week) 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 five hour cells, always all five, height = the 5h points that hour added (each positive step between consecutive samples credited to the hour the later sample fell in). Fixed width makes it an axis rather than a bar that grows at you, and it makes the hollow run afterโฎthe answer to how long have I got:โโโฎโฏโฏis two whole hours left after this one, no arithmetic and no second glance at the clock. What it does not draw is a forecast โ noรwall. An empty cell is a fact (that hour has not happened); a dry one is a guess, and the guess is already owned twice, by the badge (5h[38%@23:00]) and by the notice (5h caps ~14:20, with its own gates and an exact time).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 only while folding pays: with more than ten cells ahead, two hollow cells afterโฎ, then...โฏ(โ28)โ 28 more 5h windows after the one you are in (รred when the tail projects dry). That count is the budget line's own~28โ5h left, priced from the same instant: one row, one arithmetic. Ten cells or fewer โ the last ~2 days of every week โ draw in full, because the cells stopped being all alike: once the learned rest model knows your hours, a hollow cell you sleep through most of draws dim, so the drawn tail is a shape (tonight, the night, tomorrow morning), not a count. 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 |
โ |
the baseline: ran and cost under a point, or ran idle inside the log's coverage. The shortest bar of the same Block Elements run as โโโ, so the zero line shares their font, width and height โ it used to be ห (U+02CD), a modifier letter, which terminals resolve through the text face and which broke the strip's metrics mid-row. Override with LEDGER_BASE_GLYPH |
โ |
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. On the 5h strip the run of them is the hours left in the window. Dim when your learned hours say you sleep through most of it (under half the cell awake): capacity on the grid that is not really available. A reader who cannot see the tint loses nothing actionable: a dim โฏ is still a window ahead |
ร |
the dry mark โ in the folded token only (...ร(โ28): the tail projects dry). A future cell is a slot, never a verdict: the wall belongs to the notices, which state the exact time (7d dry ~Tue 22:00, 5h caps ~14:20) instead of overwriting windows on the grid |
...โฏ(โ28) |
the folded 7d future: 28 more 5h windows after the one you are in, one token instead of a run of hollow cells (ร red when the tail ends dry). The count is what the budget line prices โ not the number of cells the fold happened to hide |
โ |
not a cell โ the multiplication sign, the row's one operator (...โฏ(โ28), 0.7โ, 19โ5h left). Deliberately not ร (U+00D7), which is already a reading: cells are the ink, the operator is punctuation, and ...ร(ร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 next notice in full โ never the pin's own sentence, which row 2 is already carrying โ and only for ~90 s after that condition first appears in this session: the explanation arrives once, then gets out of the way and leaves the pin. One thing to say means one row, not the same sentence at two lengths. A flash that truncation has cut below a readable sentence is dropped rather than shown as a stub. 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 capped ยท /low-priority uses 7d |
The shorter session window bound while the weekly pool still has room, and this session's transcript says Claude Code actually offered lower-priority mode. The statusline does not infer eligibility from headroom. |
+ lower priority ยท 47% of 7d left |
This session enabled the bypass. Requests may wait for spare capacity and now draw against the weekly pool; the lower-priority allowance itself is not exposed to statusline scripts, so no allowance percentage is guessed. |
! 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, and the learned hour-of-day shape puts the dry point where you are awake to act on it instead of at 03:00. 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. |
+ fb ~22% expires at this mix |
The mirror of the row above: the account caps first, so the model's remaining pool cannot all be spent. Both counters run from one reset instant, so their live ratio is this week's mix rate: at 81% / 63% the 19 account points left carry ~15 of the model's 37, and the other 22 expire where nothing can reach them. Running the scoped model heavier is what moves the mix. |
+ 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 ยท lands ~52% |
The calm budget: runway plus the destination. The long form carries all three clauses โ budget ~19โ5h left ยท ~13 awake ยท even 1.6%/win ยท lands ~52% โ and names each for what it is: even N%/win is the ration (spend that per window and the pool lands exactly on 100), lands ~N% is the prediction (spend like you have been and you end up here). ~13 awake is how many of those windows fall in hours you have actually burned in (the learned hour_profile); it shows only when it cuts the count, and even then divides by it โ a ration you can only hit by not sleeping is not a ration. Row 2 keeps the landing, because the count is already drawn on the strip beside it and the ration is surplus รท count, but where the week ends up is nowhere else on screen; with no projection yet the row falls back to the ration. 19 counts the windows after the one you are in โ the window you are inside is where you are, not what you have left, and it is already drawn as โฎ and priced by 5h[38%]. The count holds steady inside a window and steps down by one at each rollover. It wears no sigil โ ! and + interrupt, the week's resting reading does not. |
--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.
At a spent session window, line 1 renders 5h[cap@04:00] rather than
5h[100%] or the occasional API value 5h[101%]. Once the window binds,
cap is the state and the reset is the number to plan around.
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)
rhythm: rest ~00:00-08:00 ยท 16h awake/day (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.
At a 5h cap, stdin keeps 5h/7d live and API-only scoped/extra fields continue
on a two-minute TTL; the cache is never frozen to the session reset.
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/435 tests across t/statusline.bats (417 statusline + integration) and
t/install.bats (18 installer) โ make check runs shellcheck first. 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
Makefile make install / status / check
install.sh Installer: curl one-liner or STATUSLINE_SRC=<checkout>
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.