How to Set Up the Claude Code Status Line
Claude Code's status line runs your script and batches updates at 300ms. How to configure it, what JSON the script receives, and a working bash + jq script.
- The status line shows whatever your script prints; Claude Code sends session data to it as JSON on stdin.
- Configure it with /statusline in plain language, or add a statusLine block (type, command, padding, refreshInterval) to your settings.
- Useful fields include model.display_name, workspace.current_dir, cost.total_cost_usd (a local estimate) and context_window.used_percentage.
The status line is a row at the bottom of Claude Code that shows the output of a script you write. Claude Code sends session data to the script as JSON on stdin and displays whatever the script prints to stdout. This guide covers how to configure it, the JSON the script receives, when it updates, a minimal script, a few extras, and what to check when nothing shows up. In our test environment (macOS, Claude Code 2.1.306, jq 1.7.1, on 2026-10-22), we piped dummy JSON into the script and checked its output. We didn’t watch it render inside a Claude Code session (not run).
Two ways to configure it
The first is the /statusline command. Describe what you want in plain language, and Claude Code writes a script into ~/.claude/ and updates your settings. To remove it, ask with something like /statusline delete.
/statusline show model name and context percentage with a progress bar
The second is to add a statusLine block to your settings yourself, in user settings (~/.claude/settings.json) or project settings:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2,
"refreshInterval": 5
}
}
| Key | What it does |
|---|---|
type | Set to "command" |
command | A script path, or an inline shell command |
padding | Extra horizontal spacing in characters. Defaults to 0 |
refreshInterval | Re-runs the command every N seconds on top of event-driven updates. Minimum 1. Leave it unset to run only on events |
Source: https://code.claude.com/docs/en/statusline (checked 2026-10-22)
For where settings files live and which one wins, see Claude Code settings.json Permissions: A Cheat Sheet.
The JSON your script receives
These are the fields you’ll use most. There are more in the docs.
| Field | Contents |
|---|---|
model.id, model.display_name | Current model ID and display name |
workspace.current_dir | Current working directory; cwd holds the same value |
workspace.project_dir | Directory where Claude Code was launched |
cost.total_cost_usd | Estimated session cost in USD, computed locally at list price. May differ from your bill |
cost.total_duration_ms | Session wall-clock time in milliseconds |
cost.total_lines_added, cost.total_lines_removed | Lines added and removed |
context_window.used_percentage | Context usage, calculated from input tokens only |
context_window.context_window_size | Context window size in tokens |
rate_limits.five_hour.used_percentage | Share of the 5-hour limit used |
rate_limits.seven_day.used_percentage | Share of the 7-day limit used |
session_id, version | Session ID and Claude Code version |
Some fields are null until the first API response arrives, so give them a fallback such as jq’s // 0. rate_limits appears only for claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit, and only after the session’s first API response. Each window can be absent on its own, so use // empty in jq to print nothing when it’s missing.
Source: https://code.claude.com/docs/en/statusline (checked 2026-10-22)
When it updates
The script runs once when a session starts, including on resume. After that it runs again when:
- A new assistant message arrives
/compactfinishes- The permission mode changes
- Vim mode toggles
- You change
commandinstatusLine - A
refreshIntervaltimer elapses, if you set one - A rate-limit window in the data the script last received reaches its
resets_attime - A warm prompt cache in the data the script last received reaches its
expires_attime
Updates are debounced at 300ms, so rapid changes batch into one run. A change to command skips the debounce and runs right away. If a new update fires while the script is still running, the in-flight run is cancelled. The status line runs locally and doesn’t use API tokens.
Source: https://code.claude.com/docs/en/statusline (checked 2026-10-22)
A minimal script
This one prints model | directory | git branch | context usage:
#!/bin/bash
# ~/.claude/statusline.sh: model | directory | git branch | context
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')
BRANCH=$(git -C "$DIR" branch --show-current 2>/dev/null)
echo "[$MODEL] ${DIR##*/} | ${BRANCH:-no git} | ctx ${PCT}%"
Make it executable with chmod +x ~/.claude/statusline.sh. We made a dummy repository at /tmp/todo-app, switched it to the branch feature/due-dates, and piped in JSON pointing at it:
$ echo '{"model":{"display_name":"Sonnet"},"workspace":{"current_dir":"/tmp/todo-app"},"context_window":{"used_percentage":23}}' | ~/.claude/statusline.sh
[Sonnet] todo-app | feature/due-dates | ctx 23%
With used_percentage set to null it printed ctx 0%. Pointed at /tmp, which isn’t a git repository, it printed no git. All exited 0. A current_dir written as a ~/projects/todo-app string printed no git, because git -C doesn’t expand the tilde.
We also tried timing 10 runs in a row, but our test machine was busy with other work that day and the numbers swung widely, so we don’t quote them here. The practical point stands: a slow script blocks the status line from updating until it finishes, and an update that arrives while it is still running cancels it, so keep the script fast.
Extras
- Multiple lines: each
echobecomes its own row - Colors: ANSI escapes such as
\033[32m, if your terminal supports them - Links: OSC 8 escapes make text clickable. iTerm2, Kitty and WezTerm support it; Terminal.app doesn’t
- Width:
tput colscan’t read the terminal size inside the script. Read theCOLUMNSandLINESvariables Claude Code sets - Usage limits: you can display the
rate_limitspercentages. For what to do when you hit a limit, see When You Hit Claude Code’s Usage Limit
Keep heavy work out of the script. The docs note that git status and git diff can be slow in large repositories, and show an example that caches git output in a temp file for 5 seconds. Name the cache file per session using session_id; process IDs change on every run and won’t work.
Source: https://code.claude.com/docs/en/statusline (checked 2026-10-22)
Not the same as output styles
An output style is a set of instructions that changes Claude’s role, tone and response format for a whole session. It changes how Claude answers, not what appears at the bottom of the screen. The status line JSON does include the current style’s name as output_style.name, so you can display it.
Sources: https://code.claude.com/docs/en/output-styles and https://code.claude.com/docs/en/statusline (checked 2026-10-22)
When nothing shows up
| Symptom | What to check |
|---|---|
| Nothing appears | The script is executable (chmod +x), it prints to stdout rather than stderr, and it produces output when you run it by hand |
| It goes blank | A non-zero exit code or empty output isn’t displayed |
| Blank in a new folder | The status line doesn’t run until you accept the workspace trust dialog |
Shows -- or empty values | Some fields are null before the first response; add a fallback like // 0 |
| Still stuck | claude --debug logs the script’s stderr |
Source: https://code.claude.com/docs/en/statusline (checked 2026-10-22)
In our runs, the script with its execute bit removed failed with Permission denied and exit 126. With jq removed from PATH, it printed jq: command not found to stderr, then a broken line, [] | no git | ctx %, and exited 0. Without jq, you get no error, just a garbled status line. Many script problems here share causes with hooks; see Claude Code Hooks Not Working? A Checklist.
Summary
- The status line shows the stdout of a script that receives session JSON on stdin.
- Configure it with
/statusline, or writetypeandcommandunderstatusLinein settings. - Use
context_window.used_percentagefor context andcost.total_cost_usdfor cost, with fallbacks fornull. - Updates are event-driven and debounced at 300ms. Use
refreshIntervalfor time-based values. - Cache slow work like git, keyed by
session_id, and keep the script fast. - If it’s blank, check the execute bit, stdout, exit code 0, and workspace trust.