The Claude Code statusline is a customizable bar at the bottom of Claude Code that runs a script of your choice. Claude Code passes the session data to the script as JSON on standard input and shows whatever the script prints. That way you always see how full the context window is, what the session would have cost at list price and, on a Pro or Max subscription, how much of your 5-hour and 7-day limits you have used. There are two ways to set it up: describe what you want to see with /statusline in a session, and Claude Code writes the script and the setting for you. Or add a statusLine field with "type": "command" and the path to your script to ~/.claude/settings.json. According to Anthropic, the statusline runs locally and does not consume API tokens.
I work with Claude Code every day. On my Mac, my statusline has shown the name of the current folder, the model, context usage in percent and usage of the 5-hour window since April 2026. The examples below come from Anthropic's docs; I checked the syntax against them on October 26, 2026. Everything about Claude is collected on my Claude page; if you have not set up Claude Code yet, start with Install Claude Code: Windows, Mac, Linux.
Statusline or a mod's status line: two different things
Since October 2026 there has been a second way to fill a line below the prompt, and it is easy to mix the two up. A mod, meaning a plugin with its own JavaScript or TypeScript code, can set a line below the prompt with $.ui.status(text). According to the docs, that line always starts with ⚠ and the mod's name, as in ⚠ my-mod: checks: 3 passing, and stays until the mod changes it.
So if you see a line with ⚠ and an unfamiliar name, it comes from a mod, not from your statusline. What else mods can do and how to control them in a team is covered in Claude Code Mods: Examples, Risks, Control. Where the status line sits next to skills, hooks, plugins and mods is sorted out in Claude Code Extensions: Plugins, Mods, Skills.
Set up the statusline with /statusline
The quickest way: in a running session, describe in plain language what you want displayed. The example from the docs:
/statusline show model name and context percentage with a progress bar
Claude Code then creates a script in ~/.claude/ and adds it to your settings. Approve the prompts to write the files. To remove it, give /statusline a matching instruction; the docs mention /statusline delete or /statusline remove it, or you delete the statusLine field from settings.json by hand.
Set up the statusline manually
If you want to understand what /statusline builds behind the scenes, set it up yourself. The docs walk through three steps for a script on macOS and Linux that shows the model, folder and context usage. It uses jq, a command-line JSON tool you may need to install first.
1. Create the script at ~/.claude/statusline.sh:
#!/bin/bash
# Read JSON data that Claude Code sends to stdin
input=$(cat)
# Extract fields using jq
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
# The "// 0" provides a fallback if the field is null
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# Output the status line - ${DIR##*/} extracts just the folder name
echo "[$MODEL] 📁 ${DIR##*/} | ${PCT}% context"
2. Make it executable:
chmod +x ~/.claude/statusline.sh
3. Add it to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}
Claude Code reloads settings when you save and runs the script right away. Instead of your user settings, you can also put the statusline in project settings. The docs list three optional fields: padding indents the output by a number of characters, refreshInterval also re-runs the script every N seconds (minimum 1), and hideVimModeIndicator hides the built-in -- INSERT -- text when your script shows the vim mode itself.
A quick word on timing: the script runs when a session starts and then on every new response, after /compact, when the permission mode changes, and when a limit window or the prompt cache expires. Claude Code batches changes within 300 milliseconds and cancels a script that is still running when the next update is due. So keep your script fast.
Example 1: keep an eye on context
The most useful field day to day is context_window.used_percentage: the share of the context window that the last response occupied. This script from the docs turns it into a ten-character bar:
#!/bin/bash
# Read all of stdin into a variable
input=$(cat)
# Extract fields with jq, "// 0" provides fallback for null
MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# Build progress bar: printf -v creates a run of spaces, then
# ${var// /▓} replaces each space with a block character
BAR_WIDTH=10
FILLED=$((PCT * BAR_WIDTH / 100))
EMPTY=$((BAR_WIDTH - FILLED))
BAR=""
[ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /▓}"
[ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /░}"
echo "[$MODEL] $BAR $PCT%"
Two details from the docs help you read the number. First, used_percentage counts input tokens only, meaning fresh input plus cache write and cache read tokens, not output. Second, the statusline shows the state of the last response, while /context estimates the messages added since. So /context can read slightly higher until the next response. The window size is in context_window.context_window_size: according to the docs, 200,000 tokens by default or 1,000,000 for models with extended context.
Example 2: cost and duration
The cost.total_cost_usd field adds up the estimated cost of all API calls in the session, and cost.total_duration_ms the running time. The script from the docs formats both:
#!/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"
Read the amount correctly: according to the docs, Claude Code calculates it on your machine at list price, unless your organization sets its own rates with modelPricing. It may differ from your actual bill and resets to 0 with /clear, because that starts a new session. On a subscription, it is mainly a reference for how much a session would have cost through the API.
Example 3: the 5-hour and weekly limit
On a Pro or Max subscription, Claude Code provides two windows in the rate_limits field: five_hour for the rolling 5-hour window and seven_day for the week, each with used_percentage from 0 to 100 and resets_at for when it resets. The script from the docs shows both and also handles a company gateway with a spend limit:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
# "// empty" produces no output when rate_limits is absent
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")%"
# Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
SPEND_PCT=$(echo "$input" | jq -r '.rate_limits.spend_limit.used_percentage // empty')
SPEND_USD=$(echo "$input" | jq -r '.rate_limits.spend_limit | select(.used_usd != null) | "$\(.used_usd) / $\(.limit_usd)"')
[ -n "$SPEND_PCT" ] && LIMITS="${LIMITS:+$LIMITS }spend: ${SPEND_USD:-$(printf '%.0f' "$SPEND_PCT")%}"
[ -n "$LIMITS" ] && echo "[$MODEL] | $LIMITS" || echo "[$MODEL]"
The docs name three restrictions, and most attempts fail on one of them:
- Pro and Max only:
rate_limits exists for claude.ai Pro and Max subscriptions. With an API key and no gateway, you see nothing there.
- Only after the first response: before a session's first API response the field is missing, and each window can be missing on its own. That is what the
// empty in the script is for.
- Spend limit only behind a gateway:
rate_limits.spend_limit only appears when your organization runs Claude Code through a Claude apps gateway with a spend limit. The field has existed since version 2.1.251, the dollar amounts used_usd and limit_usd since 2.1.284, in both Claude Code and the gateway. On Pro or Max you will look for it in vain.
Multiple lines, colors and links
Every line your script prints becomes its own row in the statusline. You set colors with ANSI codes such as \033[32m for green, and clickable links with OSC 8 sequences; according to the docs these work in iTerm2, Kitty and WezTerm, for example, but not in macOS Terminal. The docs include a two-line example: model, folder and git branch on top, and below a context bar that is green under 70 percent, yellow from 70 and red from 90, next to cost and duration. The more escape sequences and lines, the more likely display glitches become, according to the docs; when in doubt, a plain single line is the most robust.
Cache expensive commands such as git status, because the script runs very often. The docs recommend a file name built from the session_id in the JSON rather than the process ID, which changes on every call. Your script reads the terminal size from the COLUMNS and LINES environment variables; tput cols does not work there.
Statusline on Windows
On Windows, Claude Code runs the command through Git Bash if it is installed, otherwise through PowerShell. Important: write paths in the command field with forward slashes. Otherwise Git Bash swallows the backslashes and the statusline stays blank without an error. According to the docs, you call a PowerShell script like this:
{
"statusLine": {
"type": "command",
"command": "powershell -NoProfile -File C:/Users/username/.claude/statusline.ps1"
}
}
The matching statusline.ps1 from the docs shows folder, model and context:
$input_json = $input | Out-String | ConvertFrom-Json
$cwd = $input_json.cwd
$model = $input_json.model.display_name
$used = $input_json.context_window.used_percentage
$dirname = Split-Path $cwd -Leaf
if ($used) {
Write-Host "$dirname [$model] ctx: $used%"
} else {
Write-Host "$dirname [$model]"
}
More fields worth using
Besides context, cost and limits, the JSON holds a few more fields that help day to day:
prompt_cache (since 2.1.251): whether the prompt cache is still warm (warm), the hit ratio (hit_ratio) and, since 2.1.260, the likely cause of the last cache miss (last_miss_cause), such as changed tools or an expired cache.
effort.level: the current reasoning effort, from low to max, including after a change with /effort.
session_name, pr.number and worktree.name: useful when you have several sessions open in parallel and want to tell them apart.
subagentStatusLine: a separate setting that lets you format the rows of your subagents in the agent panel below the prompt. Since 2.1.293, the script there also receives the subagent type (agentType).
If you would rather not write scripts yourself: the docs mention community projects such as ccstatusline and starship-claude with ready-made configurations. I have not tested them; check third-party scripts first, because the statusline runs a shell command with your privileges.
When the statusline stays blank
The docs list these checks for troubleshooting. In my words:
- Executable?
chmod +x ~/.claude/statusline.sh.
- Output on stdout, exit code 0? Claude Code only shows the output when the script exits with 0. Output on stderr does not appear.
- Test by hand with sample JSON, as the docs suggest:
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test-session-abc"}' | ./statusline.sh
- Folder trusted? Until you accept the workspace trust dialog, the statusline stays blank, because like a hook it runs a shell command.
- Turned off?
disableAllHooks: true outside managed settings also turns off the statusline. If your organization sets allowManagedHooksOnly, your own statusline disappears without warning.
- Look at the error output:
claude --debug logs the script's stderr.
If the statusline shows -- or empty values, the fields are often still null before a session's first response. Handle that in your script, for example with // 0 in jq.
Frequently asked questions
What is the Claude Code statusline?
A customizable bar at the bottom of Claude Code. It runs a script that receives the session data as JSON and shows its output, for example the model, context usage, cost, git branch or limits.
How do I set up the statusline?
The quickest way is /statusline with a description of what you want to see. To do it by hand, add a statusLine field with "type": "command" and the path to an executable script to ~/.claude/settings.json.
Does the statusline cost tokens?
No. According to Anthropic, it runs locally and does not consume API tokens. What it costs is compute time on your machine, which is why the script should be fast.
Why doesn't my statusline show a 5-hour limit?
According to the docs, the rate_limits field exists only for Pro and Max subscriptions, and only after the first response in a session. With an API key and no gateway it stays empty, and the spend_limit field only appears behind a Claude apps gateway with a spend limit.
What is the difference between the statusline and a mod's status line?
The statusline is your own script from your settings. A mod's status line is set by an installed plugin with $.ui.status; it always starts with ⚠ and the mod's name. The two run independently of each other.
Does the statusline work on Windows?
Yes. Claude Code uses Git Bash if it is installed, otherwise PowerShell. Write paths in the command field with / instead of \, or the statusline stays blank under Git Bash.
Where to go from here
Start with /statusline and a single value, such as context usage, and only add more once you notice what you are missing day to day. Once your statusline works, hooks are worth a look next; they run shell commands at fixed points in the same way: Claude Code Hooks: Rules That Always Apply. If you run several sessions in parallel, Claude Code Agent View: Sessions Without tmux helps as well. How others set up their Claude Code environment is what we discuss in my German-speaking community Claude Practitioners.