What is the Claude Code statusline?
The Claude Code statusline is a customizable bar at the bottom of the terminal that runs a shell script you choose and shows whatever it prints. Claude Code sends the script JSON about the session on stdin, such as the model, folder, context usage and cost, so you can keep that information visible while you work. You set it up with the /statusline command or a statusLine entry in settings.json.
The official statusline docs suggest it for four jobs: watching context window usage, tracking session cost, telling several sessions apart, and keeping the git branch in view. It runs locally and does not consume API tokens.
Your status line renders in its own row above the built-in footer and does not replace it. One trade-off: with a custom status line, Claude Code stops showing most of the footer's keyboard hints, such as esc to interrupt.
The fast way: the /statusline command
/statusline takes a plain-language description and does the work for you. Claude Code generates a script file in ~/.claude/ and updates your settings.
/statusline show model name and context percentage with a progress bar
Approve the file edit prompts if Claude Code asks. Run /statusline with no description and, according to the commands reference, it auto-configures from your shell prompt.
To turn it off later, ask the same command: /statusline delete, /statusline clear or /statusline remove it all work.
Configure statusLine in settings.json
If you want to write the script yourself, add a statusLine field to your user settings at ~/.claude/settings.json, or to project settings if the whole team should share it.
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2
}
}
The fields, as listed in the settings reference:
| Field | Required | What it does |
|---|---|---|
type |
Yes | Always "command" |
command |
Yes | A script path or an inline shell command |
padding |
No | Extra horizontal spacing in characters. Defaults to 0 |
refreshInterval |
No | Also re-run every N seconds (minimum 1), for clocks or data that changes while the session is idle |
hideVimModeIndicator |
No | Set true to hide the built-in -- INSERT -- text when your script shows the Vim mode itself |
Because command runs in a shell, you can skip the script entirely for something simple. This one-liner from the official docs uses jq to print the model and context percentage:
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}
Claude Code reloads settings automatically, so the status line appears as soon as you save.
What JSON does the status line receive?
Claude Code pipes a JSON object to your script's stdin every time it runs. These are the fields most people use; the official page lists the full schema.
| Field | What it holds |
|---|---|
model.id, model.display_name |
Current model ID and display name |
workspace.current_dir |
Current working directory (same value as cwd) |
workspace.project_dir |
Folder where Claude Code was launched |
workspace.repo.owner, workspace.repo.name |
Repo identity from the origin remote, when there is one |
context_window.used_percentage |
Percentage of the context window used |
context_window.remaining_percentage |
Percentage remaining |
context_window.context_window_size |
Max context size in tokens |
cost.total_cost_usd |
Estimated session cost in USD, computed on your machine |
cost.total_duration_ms |
Wall-clock time the session has run |
cost.total_lines_added, cost.total_lines_removed |
Lines of code changed |
effort.level |
Current effort level, when the model supports it |
rate_limits.five_hour.used_percentage |
Share of the 5-hour limit used (Pro and Max) |
rate_limits.seven_day.used_percentage |
Share of the 7-day limit used (Pro and Max) |
session_id, session_name |
Session ID, and its name if it has one |
pr.number, pr.url |
Open pull request for the current branch, when found |
vim.mode |
Vim mode, when Vim editing is on |
version |
Claude Code version |
Two details save debugging time:
- Some fields can be missing.
rate_limitsappears only for claude.ai Pro and Max subscribers (or behind a gateway with spend limits) and only after the first API response.effort,vim,pr,session_nameandworkspace.repoalso appear only when they apply. - Some fields can be null.
context_window.used_percentagemay benullearly in a session, andcontext_window.current_usageisnullbefore the first API call and right after/compact.
In jq, use // 0 for a numeric fallback or // empty to print nothing when a field is absent. Note that used_percentage counts input tokens only, not output tokens. Also, cost.total_cost_usd is an estimate at list price and may differ from your bill.
Example scripts
Each example below is a bash script that uses jq, a command-line JSON parser you may need to install. To use one, save it as ~/.claude/statusline.sh, make it executable, and point statusLine.command at it.
chmod +x ~/.claude/statusline.sh
Model, folder and context
The starter script from the official docs. ${DIR##*/} keeps just the folder name.
#!/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)
echo "[$MODEL] ${DIR##*/} | ${PCT}% context"
Git branch with colors
This adds the branch plus counts of staged (green) and modified (yellow) files, using ANSI color codes.
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
GREEN='\033[32m'
YELLOW='\033[33m'
RESET='\033[0m'
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
GIT_STATUS=""
[ "$STAGED" -gt 0 ] && GIT_STATUS="${GREEN}+${STAGED}${RESET}"
[ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${YELLOW}~${MODIFIED}${RESET}"
echo -e "[$MODEL] ${DIR##*/} | $BRANCH $GIT_STATUS"
else
echo "[$MODEL] ${DIR##*/}"
fi
Cost and duration
Formats the estimated cost as dollars and the session time as minutes and seconds.
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
COST_FMT=$(printf '$%.2f' "$COST")
DURATION_SEC=$((DURATION_MS / 1000))
MINS=$((DURATION_SEC / 60))
SECS=$((DURATION_SEC % 60))
echo "[$MODEL] $COST_FMT | ${MINS}m ${SECS}s"
Plan usage limits
For Pro and Max subscribers, this shows how much of the 5-hour and 7-day limits you have used. // empty keeps it quiet when the fields are absent.
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
LIMITS=""
[ -n "$FIVE_H" ] && LIMITS="5h: $(printf '%.0f' "$FIVE_H")%"
[ -n "$WEEK" ] && LIMITS="${LIMITS:+$LIMITS }7d: $(printf '%.0f' "$WEEK")%"
[ -n "$LIMITS" ] && echo "[$MODEL] | $LIMITS" || echo "[$MODEL]"
Each echo prints a separate row, so you can combine these into a two-line status line: identity and git on the first line, context and cost on the second.
When does the status line update?
Your script runs when a session starts or resumes, then again when:
- a new assistant message arrives
/compactfinishes- the permission mode or Vim mode changes
- you change the
commandin your settings - a
refreshIntervaltimer elapses, if you set one - a rate-limit window in the last data your script received reaches its
resets_attime - a warm prompt cache in the last data your script received reaches its
expires_attime
Claude Code debounces updates at 300ms, so a burst of changes triggers one run. If a new update arrives while your script is still running, the old run is cancelled. Edits to your script show up the next time something triggers a run.
Tips for a fast, readable status line
- Test with mock input before wiring it up. The official docs suggest piping sample JSON straight into the script:
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test-session-abc"}' | ./statusline.sh
- Keep it short. Long output gets truncated or wraps badly, especially on narrow terminals where notifications share the row.
- Keep it fast. The script runs often. Slow commands like
git statusin a big repo can make it lag; the official page has a caching example that stores git info in a temp file keyed bysession_idand refreshes it every 5 seconds. - Read the width from the environment.
tput colscannot see the terminal from inside the script. Use theCOLUMNSandLINESvariables, which Claude Code sets before running it. - Use Python or Node if you prefer. The docs show the same examples in both. Anything that reads stdin and prints to stdout works.
If you prefer something ready-made, the official page points to community projects such as ccstatusline and starship-claude.
Why is my status line blank?
Work through these in order:
- Is the script executable? Run
chmod +x ~/.claude/statusline.sh. - Does it print to stdout? Output on stderr is not shown.
- Does it exit with code 0? A non-zero exit or empty output leaves the bar blank.
- Have you trusted the folder? The status line runs a shell command, so it waits for the workspace trust dialog, the same rule that applies to hooks.
- Is
disableAllHooksset? Outside managed settings, it disables your status line too. If your organization setsallowManagedHooksOnly, only a managed status line runs. - Still stuck? Start with
claude --debugto log the script's stderr, or ask Claude to read your settings and run thestatusLinecommand for you.
If values show as empty or --, they are probably null before the first API response. Add fallbacks such as // 0.
Status lines for subagents
There is a second, related setting. subagentStatusLine replaces the default row shown for each running subagent in the panel below the prompt. It takes the same type and command shape, receives a tasks array on stdin, and expects one JSON line back per row you want to override. Most people never need it, but it is handy if you run several subagents and want to see their model or token use at a glance.
Once you have a status line you like, keep the script with the rest of your setup. Kipt stores prompts and skills as Markdown in a private GitHub repo you own, which makes it a natural home for the /statusline description that produced a layout you want to reuse on another machine.