before: ~/code/myapp main
after: ctx 48% | 5h 14% ~1h20m | 7d 21% ~5d10h | opus-4.8 ~/code/myapp main
One command, and your Claude Code statusline now shows how full your context is and how close you are to the Pro/Max limits, each with a reset countdown, in front of whatever bar you already run.
Claude Code hides both of those numbers until you slam into them: the context compacts out from under you, or you hit a usage limit mid-task, with no warning. And if you write hooks or plugins, you cannot read those numbers at all. A PreToolUse
hook that wants to checkpoint before the context fills has no way to ask "how full are we". The data is not in the hook payload. Here is the fix, and the one place Claude Code will actually tell you these numbers.
There is exactly one place Claude Code exposes this. Not the hooks API, not a plugin call, not an environment variable: the statusLine
command. Every render, Claude Code pipes a JSON payload to your configured statusline program, and that payload contains the authoritative fields:
{
"session_id": "abc123",
"context_window": { "used_percentage": 47.2, "context_window_size": 200000 },
"rate_limits": {
"five_hour": { "used_percentage": 12, "resets_at": 1783359600 },
"seven_day": { "used_percentage": 30, "resets_at": 1783368000 }
},
"model": { "id": "claude-opus-4-1" }
}
context_window.used_percentage
is the real context fullness. rate_limits
(present on Pro/Max OAuth) is your 5h and 7d usage, each with a Unix-epoch resets_at
. This is the only place Claude Code shows them. So if you want the numbers, the statusline is where you have to stand.
cc-context-telemetry is a small statusLine wrapper that does two things with that payload.
One, it puts the numbers on your bar (the after line at the top). It prepends context used %, the 5h and 7d limits each with a reset countdown, and the current model to whatever your own statusline already prints. It wraps ANY statusline command, not a specific one, so you keep your existing bar and get the segment in front of it.
Two, it writes that telemetry to a per-session file your hooks can finally read. This is the part that is not obvious. Because the wrapper is standing in the one place Claude Code shows the data, it can persist it for everything else. Your hooks read it back with a one-liner.
npm i -g cc-context-telemetry
Then set it as your statusLine
in ~/.claude/settings.json
:
{
"statusLine": { "type": "command", "command": "cc-context-telemetry-statusline" }
}
Start a new session (settings changes take effect in a fresh one) and your bar is now ctx % | 5h % | 7d % | model
with reset countdowns.
Already run a statusline you want to keep? Point the wrapper at it and the segment prepends on the same line:
{
"statusLine": {
"type": "command",
"command": "cc-context-telemetry-statusline",
"env": { "CCT_WRAP": "<your existing statusline command>" }
}
}
Pick which segments show, and in what order, with CCT_SEGMENTS
(default ctx,5h,7d,model
):
CCT_SEGMENTS="ctx,5h,7d" # drop the model
CCT_SEGMENTS="5h,7d" # just the usage limits
This is the reason the project exists. In any hook, read the latest reading and act on it:
const { readTelemetry } = require('cc-context-telemetry');
const d = require('fs').readFileSync(0, 'utf8'); // hook stdin
const sessionId = JSON.parse(d).session_id;
const t = readTelemetry(sessionId);
if (t && t.fresh && t.contextPct >= 85) {
// near the wall: checkpoint, summarize, or
}
readTelemetry(sessionId)
returns the normalized reading (contextPct
, fiveHourPct
, sevenDayPct
, fiveHourResetsAt
, sevenDayResetsAt
, model
, and a fresh
flag that rejects stale or non-finite values), or null
if there is nothing yet. Now a plugin can throttle itself as context fills, warn before a usage window resets, or checkpoint long work before a compaction wipes the session. All of it was impossible without a bridge out of the statusline.
Claude Code runs your statusLine on every render and kills that process each time to keep it bounded. That is a hostile environment for anything heavy. Two design choices keep it clean:
readTelemetry
.exec
s your command, so it BECOMES your statusline process rather than spawning a child. What Claude Code kills each render is the whole thing. Nothing outlives a render to orphan or pile up. (An earlier design that spawned Node every render did pile up under the render-kills; the exec-through wrapper does not.)Node 18+ is needed only for the hooks API, never for the bar itself. It is cross-platform (CI-tested on Linux, macOS, and Windows via Git Bash), never throws, and never calls claude
.
The 5h and 7d numbers are account-wide, and Claude Code only refreshes them when a session makes an API call. So cc-context-telemetry shows the freshest reading across all your open sessions on the machine ("latest API call wins"): your sessions agree on one number instead of each showing its own stale one. Context % is per session and always current. (The reconciliation is per machine, so separate machines each track their own.)
Free and MIT-licensed, zero third-party dependencies.
It is one of the developer tools I build under Technical Turtle. If you also ship database changes and want to catch the migration that locks your production table before it does, the Postgres Migration Safety Auditor is the paid one.
If you build something with this telemetry in a hook, I would love to see it.