The Claude Code status line: what it is, how to set it up, and how Bumeter uses it
There is one row at the bottom of Claude Code that you fully control. Whatever your command prints shows up there, all the time. It is also the only place Claude Code tells you how much of your 5-hour and weekly limit you have used. That is why Bumeter lives there.
What the status line is
The status line is a bar under the prompt in Claude Code. You give it a shell command. Claude Code runs that command, hands it a JSON description of the current session on stdin, and shows whatever the command prints to stdout.
It does not run all the time. Claude Code runs it when a session starts or resumes, and again when:
- a new assistant message arrives,
/compactfinishes,- the permission mode or vim mode changes,
- a rate-limit window it last reported reaches its reset time,
- or a
refreshIntervaltimer fires, if you set one.
Updates are debounced at 300 ms, and if a new update comes in while your command is still running, Claude Code cancels the old run. In practice that means one rule: your command has to be fast.
What Claude Code sends it
The JSON on stdin describes the session. Here is a trimmed example with the fields this post uses:
{
"model": { "display_name": "Opus" },
"workspace": { "current_dir": "/home/me/project/bumeter" },
"transcript_path": "/home/me/.claude/projects/…/session.jsonl",
"context_window": { "used_percentage": 8, "context_window_size": 200000 },
"rate_limits": {
"five_hour": { "used_percentage": 23.5, "resets_at": 1738425600 },
"seven_day": { "used_percentage": 41.2, "resets_at": 1738857600 }
}
}
rate_limits is the interesting part. It is there only for claude.ai Pro and Max subscribers (or behind a gateway that sets a spend limit), only after the first API response of the session, and each window can be missing on its own. A good status line treats every field as optional.
How to set one up
The quick way: ask Claude Code to write it for you. It creates a script in ~/.claude/ and updates your settings:
/statusline show model name and context percentage with a progress bar
By hand: add a statusLine entry to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}
Then write the script. This one shows the model, the folder, the context used and, when Claude Code sends it, the 5-hour limit:
#!/bin/bash
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name')
dir=$(echo "$input" | jq -r '.workspace.current_dir')
ctx=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
five=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
echo "[$model] ${dir##*/} · ctx ${ctx}%${five:+ · 5h ${five%.*}%}"
Make it executable with chmod +x ~/.claude/statusline.sh. A few things worth knowing:
- Each line your command prints becomes a row, and ANSI colour codes work.
- On Windows, Claude Code runs the command through Git Bash when it is installed, otherwise PowerShell. Write paths with forward slashes.
- To remove it, run
/statusline remove it, or delete thestatusLineentry.
Why Bumeter needs it
Bumeter shows where your AI coding tokens go, from the logs Claude Code writes on your disk. Those logs say how many tokens each turn used. What they do not say is how close you are to your limit: no log line and no public endpoint gives the 5-hour or weekly percentage. The status line JSON is the one place it appears.
So Bumeter ships a status line command, bum statusline. On every refresh it:
- reads the JSON from stdin,
- writes
rate_limitsto a small file,limits.json, which the Bumeter app watches like any other log, - and prints its own part of the line.
Each reading is kept, so the app can draw your usage over time and estimate when you will run out (a burn rate, not a guess from one number). The tray icon, notifications and the bum status command all read from there.
Fitting in next to your own status line
If you have no status line yet, Bumeter's command simply becomes it. Many people already have one they like, though, and replacing it would be rude. So Bumeter wraps the command you already have, in one of two ways:
# Show Bumeter's part after your own line
"bum" statusline --around '<your command>'
# Only record the numbers; your line stays exactly as it was
{ "bum" statusline --tee 2>/dev/null || cat; } | { <your command>; }
With --around, Bumeter runs your command with the same input, prints its output unchanged, then adds its own part at the end of the last line. With --tee, it records the numbers and passes the JSON straight through to your command, which does not know Bumeter is there.
Because this runs inside Claude Code's interface, a few rules are not negotiable:
- It never fails. Every error is swallowed. A crash or an error message would break Claude Code's line, so the worst case is that Bumeter's part is missing.
- It is quick. The record form does no network call and reads nothing else. The show form waits at most 200 ms for the Bumeter app; if the app is slow, that refresh just has no forecast.
- It survives being uninstalled. In the record form, if
bumis gone,|| catstill hands the JSON to your command and your line keeps working. - Your settings are handled with care. The first change is backed up, writes are atomic and keep the file's permissions, and symlinked dotfiles stay symlinks. Other
statusLinefields such aspaddingare left alone. Undo restores the original file exactly when nothing else changed, keeps an edit you made to your own command in the meantime, and refuses rather than guesses when it cannot tell what you changed.
Turning it on
In the Bumeter app, open Settings → Limit tracking and click Turn on limit tracking. Before anything is written, a dialog shows the exact statusLine.command before and after. The switch Show limits and context on the Claude Code status line chooses between the two forms above. Remove puts your settings back.
If you prefer to do it yourself, point your status line at Bumeter directly:
{ "statusLine": { "type": "command", "command": "bum statusline" } }
Bumeter's Settings recognises a hook you wrote by hand and rebuilds it with the full path to bum when you switch modes. If it cannot rebuild your hook without losing part of it, it says so and changes nothing.
What it looks like
Bumeter's part has up to three pieces: the 5-hour window with its reset time, the 7-day window, and the context the session carries right now.
- The dot is green below 70%, yellow from 70% (or when the forecast says you will run out before the reset), red from 90% or when you are blocked, and grey when the numbers are old.
- ↻15:10 is when the 5-hour window resets, in your local time.
- out ~14:32 appears only when, at your current pace, you will hit the limit before it resets.
- ctx 120K is the context the session is carrying now, read from the end of the session's transcript. Past the app's "large context" threshold (300K by default) it adds ⚠ /compact as a nudge. Right after a
/compactit shows nothing until the next reply tells the new size.
Next to your own line, Bumeter leaves out a "5h —" placeholder when there are no numbers yet, so it adds nothing until it has something to say. The text follows the app's language setting, English or Vietnamese.
Bumeter is free and open source (GPL-3.0), and everything stays on your machine. The source is at github.com/chieund/bumeter. The status line code lives in crates/bum-cli/src/statusline.rs and crates/bum-adapters/src/claude_code/statusline.rs.