The two numbers most worth keeping on screen during a Claude Code session are context usage (how full the window is) and rate-limit usage (how much of your 5-hour and 7-day budget is gone). Both arrive pre-computed in the statusLine payload — you just have to read the right fields and render them compactly.
Context: use used_percentage, not token math
"context_window": {
"total_input_tokens": 261000,
"total_output_tokens": 18000,
"context_window_size": 1000000,
"used_percentage": 26,
"remaining_percentage": 74
}Resist the urge to compute (input + output) / size yourself — caching, system overhead, and summarization make naive token math drift from what the session actually experiences. used_percentage is the authoritative number. A useful rendering also says whichwindow you're in, since 26% of a 1M-context session is a very different situation from 26% of 200K:
const cw = d.context_window;
if (cw?.used_percentage != null) {
const size = cw.context_window_size >= 1_000_000 ? "1M"
: cw.context_window_size >= 1_000 ? Math.round(cw.context_window_size / 1000) + "K"
: String(cw.context_window_size ?? "");
parts.push(size ? `ctx ${cw.used_percentage}%/${size}` : `ctx ${cw.used_percentage}%`);
}Rate limits: percentage + time-to-reset
"rate_limits": {
"five_hour": { "used_percentage": 42, "resets_at": 1781290000 },
"seven_day": { "used_percentage": 36, "resets_at": 1781730000 }
}resets_atis a Unix timestamp (seconds). The human question is "how long until it resets?", so render a countdown, compacted to the largest two units:
function untilReset(resetsAt) {
const s = Math.max(0, resetsAt - Math.floor(Date.now() / 1000));
const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600),
m = Math.floor((s % 3600) / 60);
if (d > 0) return `${d}d${h}h`;
if (h > 0) return `${h}h${m}m`;
return `${m}m`;
}
const rl = d.rate_limits;
if (rl?.five_hour?.used_percentage != null)
parts.push(`5h ${rl.five_hour.used_percentage}% ${untilReset(rl.five_hour.resets_at)}`);
if (rl?.seven_day?.used_percentage != null)
parts.push(`7d ${rl.seven_day.used_percentage}% ${untilReset(rl.seven_day.resets_at)}`);The assembled line ends up like this — informative at a glance, 60 characters wide:
M Opus 4.8 | E high | ctx 26%/1M | 5h 42% 2h25 | 7d 36% 5d5hWhen the fields are missing
Fresh sessions, some account types, and edge states ship payloads without rate_limits entirely, or with a missing resets_at. Your script must treat every field as optional — skip the segment rather than printing undefined% or crashing (a crashed script means a blank statusline). The conditional style above (!= null guards before every push) is the whole defense.
Keeping it inside the terminal width
Context and rate segments are mid-priority: less important than the model name, more important than device metrics. Order your segments so that width-based trimming (driven by the COLUMNS environment variable — see the mechanics guide) drops the right things first. At 64 columns you want Opus 4.8 | ctx 26% | 5h 42%, not a truncated git branch.
Or skip the hand-rolling: the statusline builder ships these exact segments — context with window size, both rate limits with countdowns, width-aware trimming — as toggles with a live preview.