~ $ man show-context-window-and-rate-limits

Show context window and rate limits in your Claude Code statusline

6 min · updated

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% 5d5h

When 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.