Show the model and context usage in the Claude Code status line
Build a status line that shows the model name and context usage below the prompt: stdin JSON, the settings.json entry, null handling and re-run triggers.
Claude Code pipes session data as JSON into your script's stdin and shows whatever the script prints to stdout below the prompt. It runs locally and uses no API tokens. For what happens when the context fills up, see Context limits and compact.
[1] Check that jq is installed. The script below uses it to read the JSON.
$ jq --versionjq-1.7[2] Save the script as ~/.claude/statusline.sh. It prints the model display name, the folder name and context usage on one line.
#!/bin/bash# status line: [model] folder | context bar NN% (size)command -v jq >/dev/null || { echo "statusline: jq not found"; exit 0; }input=$(cat) MODEL=$(jq -r '.model.display_name // "?"' <<< "$input")DIR=$(jq -r '.workspace.current_dir // .cwd // ""' <<< "$input")PCT=$(jq -r '.context_window.used_percentage // 0 | floor' <<< "$input")SIZE=$(jq -r '.context_window.context_window_size // 0' <<< "$input") # non-numeric becomes 0, clamp to 0-100case "$PCT" in ''|*[!0-9]*) PCT=0 ;; esac[ "$PCT" -gt 100 ] && PCT=100 W=10FILLED=$((PCT * W / 100))BAR=""for ((i = 0; i < W; i++)); do if [ "$i" -lt "$FILLED" ]; then BAR+="▓"; else BAR+="░"; fidone if [ "$SIZE" -ge 1000000 ]; then SZ="$((SIZE / 1000000))M"; else SZ="$((SIZE / 1000))k"; fiecho "[$MODEL] ${DIR##*/} | $BAR ${PCT}% of $SZ"// 0 is the fallback when a value is null or missing. Early in a session used_percentage can be null, so the line is needed. ${DIR##*/} keeps only the last folder of the path.
[3] Make it executable.
$ chmod +x ~/.claude/statusline.sh[4] Feed it sample JSON before starting Claude Code. The field names match the schema in the official docs.
$ echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/srv/app"},"context_window":{"context_window_size":200000,"used_percentage":42.7}}' | ~/.claude/statusline.sh[Opus] app | ▓▓▓▓░░░░░░ 42% of 200kTry the state before the first response, when used_percentage is null:
$ echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/srv/app"},"context_window":{"context_window_size":200000,"used_percentage":null,"current_usage":null}}' | ~/.claude/statusline.sh[Opus] app | ░░░░░░░░░░ 0% of 200kIf the script exits with a non-zero code or prints nothing, the status line goes blank (see "Script errors or hangs" in the docs).
[5] Register it in ~/.claude/settings.json. If the file already has settings, add only the statusLine key.
{ "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" }}command can also be a one-line shell command instead of a script path. Claude Code reloads the settings on save and runs the script right away. Edits to the script file take effect on the next update.
[6] Know when it re-runs. The script runs once when a session starts (resumes included), then again on these events:
| Trigger | Note |
|---|---|
| A new assistant message | |
/compact finishes | |
| Permission mode change · vim mode toggle | |
The statusLine command changes | Runs at once, no debounce |
refreshInterval elapses | Only if you set it |
Changes that arrive together are batched by a 300 ms debounce, and the script runs once at the end. If a new update arrives while the script is still running, the running one is cancelled. A slow command makes the line update late.
[7] Add refreshInterval only when the line shows something that changes with time, or while the session sits idle. The unit is seconds and the minimum is 1.
{ "statusLine": { "type": "command", "command": "~/.claude/statusline.sh", "refreshInterval": 30 }}[8] If the line looks wrong, check in this order.
| Symptom | Check |
|---|---|
| Nothing shows | Execute bit (chmod +x), output goes to stdout, and the script prints output when run by hand as in step 4 |
| Blank in a newly opened folder | Accept the workspace trust dialog. claude --debug logs Status line command skipped: workspace trust not accepted |
Value is null or -- | It can be null before the first response. Add a fallback such as // 0 |
Each line you echo is shown as its own row, and ANSI colors and OSC 8 links work. The docs warn that multi-line output with escape codes is more prone to rendering glitches than one line of plain text. tput cols inside the script cannot read the terminal width; read the COLUMNS and LINES environment variables that Claude Code sets.