Home / Guides / Status line setup
Claude Code status line setup that shows usage and reset time
Quick answer: save a script that reads JSON from stdin, make it executable, and point the statusLine setting in ~/.claude/settings.json at it (or run /statusline and describe what you want). On a claude.ai Pro or Max plan, Claude Code sends rate_limits.five_hour and rate_limits.seven_day, each with used_percentage and resets_at. The tested script below turns them into [Opus] 5h: 24% | 7d: 41% | resets 17:00.
What is the Claude Code status line?
It is a row at the bottom of Claude Code that shows whatever a script of yours prints. Claude Code runs the script, passes it JSON about the session on stdin, and displays its stdout.
Because the data includes your claude.ai plan's rate limits, a status line can keep your 5-hour and weekly usage in view all the time, updated as you work, without you asking.
How do I set up a status line?
With the /statusline command
Type /statusline followed by what you want, in plain words:
/statusline show model name and 5-hour usage with the reset time
Claude Code writes a script into ~/.claude/ and updates your settings. Approve the file edits if it asks.
By hand in settings.json
Add a statusLine field to ~/.claude/settings.json (or to project settings). Set type to "command" and point command at a script or an inline shell command:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}
Two useful optional fields: padding adds horizontal spacing in characters (default 0), and refreshInterval re-runs the command every N seconds (minimum 1).
What JSON does Claude Code send to the script?
A single JSON object on stdin. The fields most status lines use:
model.idandmodel.display_name: the current model.workspace.current_dirandworkspace.project_dir: where you are and where Claude Code was launched.context_window.used_percentageandcontext_window.context_window_size: how full the context is.cost.total_cost_usd: an estimated session cost at list price.rate_limits.five_hourandrate_limits.seven_day: each hasused_percentage(0 to 100) andresets_at(Unix epoch seconds).
This is the sample input used to test every script on this page. It follows the documented shape, trimmed to the fields that matter here:
{
"session_id": "abc123",
"model": { "id": "claude-opus-5-5", "display_name": "Opus" },
"workspace": { "current_dir": "/Users/you/project" },
"version": "2.1.278",
"context_window": { "used_percentage": 8 },
"rate_limits": {
"five_hour": { "used_percentage": 23.5, "resets_at": 1790182800 },
"seven_day": { "used_percentage": 41.2, "resets_at": 1790586000 }
}
}
Which plans get rate_limits?
The docs say rate_limits is present only for claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit for you, and only after the first API response in the session. Each window can be absent on its own, and Claude Code drops a window once its resets_at time passes. So your script must cope with the field missing. On an API key you will not see it at all.
A script that shows usage and reset time
This prints the model, 5-hour use, weekly use and the 5-hour reset in your local time, using jq. Save it as ~/.claude/statusline.sh:
#!/bin/bash
# Claude Code status line: model, 5-hour and weekly use, 5-hour reset time.
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name // "Claude"')
# "// empty" prints nothing when a field is absent
FIVE=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
RESET=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
OUT="[$MODEL]"
[ -n "$FIVE" ] && OUT="$OUT 5h: $(printf '%.0f' "$FIVE")%"
[ -n "$WEEK" ] && OUT="$OUT | 7d: $(printf '%.0f' "$WEEK")%"
if [ -n "$RESET" ]; then
# macOS date takes -r, Linux (GNU) date takes -d @
WHEN=$(date -r "$RESET" +%H:%M 2>/dev/null || date -d "@$RESET" +%H:%M)
OUT="$OUT | resets $WHEN"
fi
echo "$OUT"
Make it executable and test it with the sample before you touch settings:
chmod +x ~/.claude/statusline.sh
TZ=UTC ~/.claude/statusline.sh < sample.json
Real output (jq 1.8.2, macOS):
[Opus] 5h: 24% | 7d: 41% | resets 17:00
Without TZ=UTC the reset time is in your own time zone; in Singapore the same input prints resets 01:00. With the rate_limits field removed, it prints just [Opus].
How do I do it without jq?
The command runs in a shell, so any language works. This python3 version needs only the standard library and prints the same line. Save it as ~/.claude/statusline.py, run chmod +x on it, and use that path as the command:
#!/usr/bin/env python3
# Claude Code status line without jq: same output as the bash version.
import json
import sys
from datetime import datetime
data = json.load(sys.stdin)
model = data.get("model", {}).get("display_name", "Claude")
rate = data.get("rate_limits", {})
five = rate.get("five_hour", {})
week = rate.get("seven_day", {})
out = f"[{model}]"
if five.get("used_percentage") is not None:
out += f" 5h: {five['used_percentage']:.0f}%"
if week.get("used_percentage") is not None:
out += f" | 7d: {week['used_percentage']:.0f}%"
if five.get("resets_at") is not None:
out += " | resets " + datetime.fromtimestamp(five["resets_at"]).strftime("%H:%M")
print(out)
Real output (Python 3.14, same sample, TZ=UTC):
[Opus] 5h: 24% | 7d: 41% | resets 17:00
How often does the status line refresh?
Claude Code runs the script once when a session starts or resumes, then again when:
- a new assistant message arrives,
/compactfinishes, the permission mode changes or vim mode toggles; - you change the
commandinstatusLine(this one skips the debounce); - a
refreshIntervaltimer elapses, if you set one; - a rate-limit window in the data it last sent reaches its
resets_attime.
Updates are debounced at 300ms, and if a new update fires while your script is still running, the running one is cancelled. When the main session sits idle the event triggers can go quiet, so set refreshInterval if you show anything time-based, such as a countdown.
Why is my status line blank or wrong?
- No output at all. A script that exits non-zero or prints nothing leaves the row blank. Print to stdout, not stderr, and run the script by hand with sample input.
claude --debuglogs the exit code and stderr of the first run in a session. - Not executable. Run
chmod +xon the script. In our test, running the python3 script without it failed with "permission denied" and exit code 126. - jq missing. With no
jqon the PATH, our jq script printed[]and wrote "jq: command not found" to stderr. Install jq or use the python3 version. - Limits absent. Expected on an API key, before the first reply in a session, or after a window's reset time passes. Use
// emptyin jq or.get()in Python so the rest of the line still prints. - Workspace not trusted. The status line runs a shell command, so it follows the same trust rule as hooks. Until you accept the trust dialog it stays blank.
- Slow or stale. A slow script holds back updates. Keep it fast and cache anything like
git status.
How do I keep my status line when a tool wraps it?
Only one statusLine command runs, so a tool that wants the same data has two choices: replace your status line, or sit in front of it. A pass-through reads stdin once, saves a copy for itself, then pipes the exact same bytes into your original command, so your status line prints what it always did:
#!/bin/bash
# Pass-through: keep a copy of the status JSON, then run your own status line.
input="$(cat; printf x)" # the x keeps any trailing newlines
input="${input%x}"
printf '%s' "$input" > "$HOME/.my-tool/status.json"
printf '%s' "$input" | ~/.claude/statusline.sh
With the sample, it printed the same line as before and the saved copy matched the input byte for byte. Before you let any tool change statusLine, check three things: it shows you the change, it runs your command unchanged, and uninstalling puts your old command back.
How Vibepanions wraps it
Vibepanions shows your Claude Code limits in a floating Mac widget, and it gets them this way. Its installer changes statusLine.command to "$HOME/.vibepanions/statusline.sh" followed by your old command in single quotes. The script:
- reads the input exactly, trailing newlines included;
- keeps it in
~/.vibepanions/statusline.jsononly when it carriesfive_hourorseven_day, so a session on an API key never overwrites good figures. The file is written to a temp file and renamed, is private to you (mode 600), and holds no credentials; - then runs your original command with the same input. With no original command it prints nothing, as before.
Install backs up settings.json first and shows the diff. Uninstall puts your original command back (or removes statusLine if you had none) and deletes the script. On Claude Code 2.1.278, on 23 Sep 2026, the saved file read 0% five-hour and 68% weekly, and the app showed the same. Until a status arrives, the app labels its numbers "estimated".
FAQ
Does the status line use up my tokens?
No. The Claude Code docs say the status line runs locally and does not consume API tokens. It only reads the JSON Claude Code already has and prints text.
Why does my status line show the model but no limits?
The rate_limits field is only sent to claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit, and only after the first API response in the session. On an API key it is absent, so the script prints just the model.
What time zone is resets_at in?
None. It is Unix epoch seconds, so your script turns it into local time: date -r on macOS, date -d @ on Linux, or datetime.fromtimestamp in Python.
Can I write a status line without jq?
Yes. The command runs in a shell, so any language that reads stdin works. The python3 script in this guide prints the same line as the jq version.
How do I remove the status line?
Run /statusline and ask it to remove or clear your status line, or delete the statusLine field from settings.json yourself.
Want your limits on screen outside the terminal too? Get Vibepanions.
Sources
Fetched 23 Sep 2026.
- Claude Code docs, Customize your status line (setup,
statusLinefields, stdin JSON,rate_limitsavailability, update triggers, troubleshooting) - Vibepanions source,
statusline.shandHookInstaller.swift(pass-through, install and uninstall) - Vibepanions
docs/findings.md, section 3 (official plan limits, verified on Claude Code 2.1.278) - jq