The bottom row of Claude Code can show far more than a blinking cursor. With a custom status line you can see your model, context use, session cost, git branch and even how much of your 5-hour limit is left, all updated live.
This guide shows the one-line setup with /statusline, the manual settings.json method, every JSON field you can print, eight copy-paste examples, Windows setup and the fixes for a blank status line.
- Fastest way: type
/statusline show model name and context percentage with a progress barinside Claude Code. It writes the script and updates your settings. - Manual way: add a
statusLineblock with"type": "command"and acommandpath to~/.claude/settings.json. - How it works: Claude Code pipes session JSON to your script on stdin and prints whatever your script writes to stdout.
- Cost: free. It runs locally and uses no API tokens.
- Most useful fields:
context_window.used_percentage,cost.total_cost_usd,rate_limits.five_hour.used_percentage,model.display_name. - Blank line? Make the script executable, trust the folder, exit with code 0, and use forward slashes on Windows.
What the Claude Code Status Line Does
The status line is a row at the bottom of the Claude Code terminal UI that runs any shell command you choose. It sits above the built-in footer badges and does not replace them.
Your script gets a JSON object describing the session and prints text. That text can be one line or several, with colours and clickable links.
People use it to:
- Watch context: see how full the context window is before Claude auto-compacts.
- Track spend: show the estimated session cost in dollars.
- Guard plan limits: show how much of the 5-hour and 7-day subscription windows you have used.
- Tell sessions apart: print the repo, branch, worktree or session name when you run several at once.
Note: once you set a custom status line, Claude Code hides most footer key hints, such as esc to interrupt and ? for shortcuts. The shortcuts still work. My Claude Code commands cheat sheet lists them all.
Quick Setup With /statusline
The /statusline slash command takes plain English. Claude Code writes a script into ~/.claude/ and adds it to your settings for you.
- Start Claude Code in any folder with
claude. Not installed yet? Follow my install guide for Windows, Mac and Linux. - Describe what you want, for example:
/statusline show model, folder, git branch and context percentage with a coloured bar. - Approve the file edits when Claude asks for permission to write the script and change
settings.json. - Send any message. The line appears straight away and refreshes after each reply.
To remove it later, run /statusline delete (or “clear” or “remove it”). You can also delete the statusLine block from your settings by hand.
Tip: be specific in the prompt. “Show 5h and 7d usage as percentages, cost with 2 decimals, and turn the context bar red above 90%” gives a much better first script than “make a nice status line”.
Manual Setup in settings.json
Prefer to write it yourself? It takes three steps on macOS or Linux. The Bash examples use jq, so install it first (brew install jq or sudo apt install jq).
1. Create the script
Save this as ~/.claude/statusline.sh:
#!/bin/bash
# Claude Code sends session JSON on stdin
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
# "// 0" is a fallback when the field is null
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
echo "[$MODEL] ๐ ${DIR##*/} | ${PCT}% context"
2. Make it executable
chmod +x ~/.claude/statusline.sh
3. Point your settings at it
Open ~/.claude/settings.json (user level) or a project’s .claude/settings.json and add:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2
}
}
Claude Code reloads settings on save, so the line shows up without a restart. You will see something like [Opus] ๐ my-app | 8% context.
Inline command, no script file
The command runs in a shell, so a short jq one-liner works too:
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}
All statusLine Options
| Key | Value | What it does |
|---|---|---|
type |
"command" |
Required. Means “run this shell command”. |
command |
Script path or inline shell | Required. What runs on each refresh. |
padding |
Number, default 0 |
Extra horizontal spacing in characters, added to the UI’s own spacing. |
refreshInterval |
Seconds, minimum 1 |
Also re-runs the script on a timer. Use it for clocks or git changes while the session is idle. |
hideVimModeIndicator |
true / false |
Hides the built-in -- INSERT -- text when your script prints vim.mode itself. |
The user file (~/.claude/settings.json) applies everywhere. A project file applies only in that repo and wins over the user file there.
Every JSON Field You Can Show
These are the fields Claude Code sends on stdin, from the official docs. Some only appear in certain situations, so always use a fallback.
| Field | What it holds |
|---|---|
model.id, model.display_name |
Model ID (for example claude-opus-5-5) and short name (Opus) |
workspace.current_dir (same as cwd) |
Current working folder |
workspace.project_dir |
Folder where you launched Claude Code |
workspace.added_dirs |
Folders added with /add-dir |
workspace.git_worktree |
Linked worktree name (absent in the main tree) |
workspace.repo.host / owner / name |
Repo parsed from the origin remote |
cost.total_cost_usd |
Estimated session cost at list price (not your bill) |
cost.total_duration_ms, cost.total_api_duration_ms |
Session wall time and time spent waiting on the API |
cost.total_lines_added, cost.total_lines_removed |
Lines changed this session |
context_window.used_percentage, remaining_percentage |
Context fill, pre-calculated |
context_window.context_window_size |
200,000 by default, 1,000,000 for extended-context models |
context_window.current_usage |
Input, output, cache write and cache read tokens of the last call |
exceeds_200k_tokens |
true when the last response passed 200k tokens |
rate_limits.five_hour.used_percentage, .resets_at |
5-hour subscription window use (0-100) and reset time (epoch seconds) |
rate_limits.seven_day.used_percentage, .resets_at |
Weekly window use and reset time |
prompt_cache.warm, prompt_cache.hit_ratio |
Whether the cache is warm, and cache reads as a share of input (0-1) |
effort.level |
low, medium, high, xhigh or max |
thinking.enabled, fast_mode |
Extended thinking and fast mode on or off |
session_id, session_name |
Unique ID and the /rename or AI-generated title |
pr.number, pr.url, pr.review_state |
Open PR (or GitLab MR) for the branch |
worktree.name, .branch, .path |
Active worktree session details |
vim.mode, agent.name, output_style.name, version |
Vim mode, --agent name, output style, Claude Code version |
rate_limits only appears for claude.ai Pro and Max subscribers (or behind a gateway with spend limits), and only after the first reply. On an API key you will not see it. See Claude Code pricing for how the windows work.The used_percentage counts input tokens only (fresh input plus cache writes plus cache reads), not output. That is why it can read lower than /context, which also estimates messages added since the last reply.
When the Status Line Refreshes
Your script runs when a session starts or resumes, then again when any of these happen:
- A new assistant message arrives
/compactfinishes- The permission mode changes or vim mode toggles
- You edit the
commandin settings - A
refreshIntervaltimer fires, if set - A rate-limit window or warm prompt cache reaches its reset or expiry time
Updates are debounced at 300 ms. If a new update fires while your script is still running, Claude Code cancels the old run, so slow scripts show stale data.
Tip: tput cols does not work inside the script because Claude Code captures the output. Read the COLUMNS and LINES environment variables instead.
8 Ready-to-Use Status Line Examples
Save each one as ~/.claude/statusline.sh, run chmod +x, and point command at it. Test any script with fake input first:
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/me/app"},"context_window":{"used_percentage":25},"session_id":"test"}' | ~/.claude/statusline.sh
1. Context progress bar
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
printf -v F "%${FILLED}s"; printf -v E "%${EMPTY}s"
echo "[$MODEL] ${F// /โ}${E// /โ} $PCT%"
Output: [Opus] โโโโโโโโโโ 30%.
2. Colour-coded context (green, yellow, red)
#!/bin/bash
input=$(cat)
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
if [ "$PCT" -ge 90 ]; then C='\033[31m'
elif [ "$PCT" -ge 70 ]; then C='\033[33m'
else C='\033[32m'; fi
echo -e "${C}ctx ${PCT}%\033[0m"
3. Git branch with staged and modified counts
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
STAGED=$(git diff --cached --numstat | wc -l | tr -d ' ')
MOD=$(git diff --numstat | wc -l | tr -d ' ')
echo -e "[$MODEL] ๐ ${DIR##*/} | ๐ฟ $BRANCH \033[32m+$STAGED\033[0m \033[33m~$MOD\033[0m"
else
echo "[$MODEL] ๐ ${DIR##*/}"
fi
4. Session cost and duration
#!/bin/bash
input=$(cat)
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
printf '๐ฐ $%.2f | โฑ๏ธ %dm %ds\n' "$COST" $((MS/60000)) $(((MS%60000)/1000))
Note: on a Pro or Max plan this dollar figure is what the session would cost on the API. It is handy for comparing, but you are not billed it. It resets to $0 after /clear.
5. 5-hour and weekly plan usage
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
FIVE=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
OUT="[$MODEL]"
[ -n "$FIVE" ] && OUT="$OUT 5h: $(printf '%.0f' "$FIVE")%"
[ -n "$WEEK" ] && OUT="$OUT 7d: $(printf '%.0f' "$WEEK")%"
echo "$OUT"
6. Two-line status line
Every echo becomes its own row. Line one shows where you are, line two shows the numbers:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
BR=$(git branch --show-current 2>/dev/null)
echo "[$MODEL] ๐ ${DIR##*/} ${BR:+| ๐ฟ $BR}"
printf 'ctx %s%% | $%.2f\n' "$PCT" "$COST"
7. Python version (no jq needed)
#!/usr/bin/env python3
import json, sys
d = json.load(sys.stdin)
model = d["model"]["display_name"]
pct = int((d.get("context_window") or {}).get("used_percentage") or 0)
cache = (d.get("prompt_cache") or {}).get("hit_ratio")
eff = (d.get("effort") or {}).get("level", "")
extra = f" | cache {cache:.0%}" if cache is not None else ""
print(f"[{model} {eff}] ctx {pct}%{extra}")
Save as ~/.claude/statusline.py and set "command": "~/.claude/statusline.py" after chmod +x.
8. Cached git info for big repos
Running git status on every refresh can lag in a large repo. Cache it for 5 seconds, keyed on session_id (not $$, which changes every run):
#!/bin/bash
input=$(cat)
SID=$(echo "$input" | jq -r '.session_id')
CACHE="/tmp/sl-git-$SID"
AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE" 2>/dev/null || stat -f %m "$CACHE" 2>/dev/null || echo 0) ))
if [ ! -f "$CACHE" ] || [ $AGE -gt 5 ]; then
git branch --show-current 2>/dev/null > "$CACHE"
fi
echo "๐ฟ $(cat "$CACHE")"
Windows Setup (PowerShell and Git Bash)
On Windows, Claude Code runs the status line through Git Bash if it is installed, otherwise through PowerShell.
Warning: always write paths in command with forward slashes, like C:/Users/you/.claude/statusline.ps1. Git Bash eats backslashes, and the line fails silently.
PowerShell status line
settings.json:
{
"statusLine": {
"type": "command",
"command": "powershell -NoProfile -File C:/Users/you/.claude/statusline.ps1"
}
}
C:/Users/you/.claude/statusline.ps1:
$data = $input | Out-String | ConvertFrom-Json
$dir = Split-Path $data.cwd -Leaf
$model = $data.model.display_name
$used = $data.context_window.used_percentage
if ($used) { Write-Host "$dir [$model] ctx: $used%" }
else { Write-Host "$dir [$model]" }
With Git Bash installed you can also use the Bash scripts above with "command": "~/.claude/statusline.sh". Install jq with winget install jqlang.jq, or use the Python version.
Pre-Built Status Lines: ccstatusline and starship-claude
If you would rather not write scripts, the official docs point to two community projects:
| Option | Best for | Effort |
|---|---|---|
/statusline prompt |
A custom line in one minute | Lowest |
| ccstatusline | Themes and a menu-driven widget picker | Low |
| starship-claude | People who already use the Starship prompt | Medium |
| Your own script | Full control, any language | Medium |
Community tools change fast, so read their README for the current install command. Anything they add still comes from the same JSON fields listed above.
Subagent Status Lines
A second setting, subagentStatusLine, lets you restyle each row in the agent panel that lists running subagents.
{
"subagentStatusLine": {
"type": "command",
"command": "~/.claude/subagent-statusline.sh"
}
}
Your script receives a tasks array (with id, name, status, tokenCount, model and more) and writes one JSON line per row, like {"id": "<task id>", "content": "<row text>"}. Leave a task out to keep its default row.
Troubleshooting
Status line not appearing at all
- Not executable: run
chmod +x ~/.claude/statusline.sh. - Wrong stream: print to stdout, not stderr.
- Non-zero exit: the line only shows after the script exits with code 0. Run it by hand with the mock input above.
- Windows path: swap backslashes for forward slashes in
command. - Hooks disabled:
disableAllHooks: truealso disables custom status lines. Company-managedallowManagedHooksOnlydoes the same.
“Status line command skipped: workspace trust not accepted”
You see this in claude --debug output. The status line runs a shell command, so it waits until you accept the folder trust dialog. Restart Claude Code in that folder and accept it.
Status line shows --, null or empty values
Fields such as used_percentage and current_usage are null before the first reply and right after /compact. Add fallbacks like // 0 in jq or or 0 in Python.
“jq: command not found”
Install jq (brew install jq, sudo apt install jq, winget install jqlang.jq) or switch to the Python or Node example.
Links not clickable or escape codes printed as text
OSC 8 links need iTerm2, Kitty, WezTerm or a similar terminal; macOS Terminal.app does not support them. Try FORCE_HYPERLINK=1 claude, and use printf '%b' instead of echo -e if you see raw \e]8;; text.
Status line is slow or out of date
Slow commands block the update and get cancelled by the next one. Cache git calls (example 8) and keep the script well under a second.
Still stuck? Run claude --debug. It logs your script’s stderr on every run and the exit code on the first run.
My Take
If you only add one thing, make it a colour-coded context bar plus the 5-hour usage figure. Those two numbers tell you when to /compact and when you are about to hit your plan limit, which saves more frustration than any other tweak.
Start with /statusline to get a working script, then edit it by hand. If you watch costs closely, pair it with my guide to prompt caching and show prompt_cache.hit_ratio on the line.
FAQ
How do I add a status line in Claude Code?
Run /statusline followed by what you want to see, or add a statusLine block with "type": "command" and a script path to ~/.claude/settings.json.
Does the status line use tokens or cost money?
No. The script runs locally on your machine and does not call the API.
How do I show the 5-hour usage limit in the status line?
Print rate_limits.five_hour.used_percentage and rate_limits.seven_day.used_percentage. They appear only for Pro and Max subscribers after the first reply in a session.
How do I remove or reset the status line?
Run /statusline delete, or remove the statusLine field from your settings.json file.
Can the status line have more than one line?
Yes. Each line your script prints becomes its own row, and ANSI colours and OSC 8 links are supported.
Why is my status line blank on Windows?
Usually a backslash path in command. Use forward slashes, and call PowerShell scripts with powershell -NoProfile -File.
Can I set a different status line per project?
Yes. Put the statusLine block in the project’s .claude/settings.json. It overrides your user setting inside that repo.




