Customize the Claude Code status line
Show the model, the folder, context use and your plan limits at the bottom of Claude Code, with a small script you write yourself or one Claude writes for you.
Checked against the official documentation on
On this page10 sections
At the bottom of Claude Code there is room for one line (or several) that you control. Claude Code runs a command you choose, passes it the session state as JSON, and shows whatever the command prints. The most useful thing to put there is how full the context window is, so a long session never catches you by surprise.
This guide covers the quick way, building a script yourself, every setting, the data you can use, a complete two-line example, and what to check when the line stays blank.
What the status line is
- It is a command. It can be a script in Bash, Python or Node.js, or a one-line shell command.
- It receives JSON on standard input, and whatever it prints to standard output becomes the status line. Each printed line is one row.
- It gets its own row above the built-in footer. It does not replace the footer, but while it is set, Claude Code hides most of the footer's keyboard hints, such as
esc to interruptand? for shortcuts. - It runs on your machine and does not use any tokens.
- It hides for a moment during autocomplete, the help menu and permission prompts.
The quick way: ask Claude
Type /statusline followed by what you want to see:
/statusline show the model name and context percentage with a progress barClaude Code writes a script in ~/.claude/ and adds it to your settings. Approve the file edits when it asks. To remove it later:
/statusline remove itThat is enough for most people. The rest of this guide is for when you want to know what it built, or build your own.
Build one yourself
The examples use Bash and jq, a small command-line JSON tool. They work on Linux and macOS. Windows is covered near the end.
Install jq
On AlmaLinux, Rocky, RHEL or Fedora:
Shell · your usersudo dnf install jqOn Debian or Ubuntu use
sudo apt install jq, and on macOS usebrew install jq.Write the script
Save this as
~/.claude/statusline.sh:~/.claude/statusline.sh#!/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"// 0gives jq a fallback when the value is missing or null, and${DIR##*/}keeps only the folder name.Make it executable
Shell · your userchmod +x ~/.claude/statusline.shTest it before Claude Code runs it
Feed it some JSON by hand. If this prints nothing or an error, Claude Code will show a blank line too.
Shell · your userecho '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/youruser/my-app"},"context_window":{"used_percentage":25}}' | ~/.claude/statusline.shOutput
[Opus] my-app | 25% contextPoint Claude Code at it
Add this to
~/.claude/settings.json, merging it with anything already in the file:~/.claude/settings.json{ "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" } }Claude Code picks up the change as soon as you save the file.
Every setting
The statusLine object takes these keys:
type: always"command".command: the script path, or a shell command written inline. For example, this works with no script file at all:jq -r '"[\(.model.display_name)] \(.context_window.used_percentage // 0)% context"'padding: extra spaces before the content, in characters. Default0. It adds to the built-in spacing, so it indents the line; it does not set the distance from the terminal edge.refreshInterval: re-runs the command every N seconds (minimum1) on top of the normal updates. Use it for a clock, or for data that changes while Claude is idle.hideVimModeIndicator: set totrueif your script showsvim.modeitself, so the-- INSERT --text does not appear twice.
The setting can go in your user settings (~/.claude/settings.json, for every project) or in a project's .claude/settings.json.
Warning
A status line runs a shell command on your machine. A project's .claude/settings.json can set one, and Claude Code runs that command once you accept the trust dialog for that folder. Before trusting a repository you did not write, read its .claude/ folder.
When it updates
The script runs when a session starts or resumes, and again when:
- a new assistant message arrives
/compactfinishes- the permission mode changes, or vim mode toggles
- you change the
commandin settings - a
refreshIntervaltimer fires - a rate-limit window or a warm prompt cache reaches the reset or expiry time from the last data
Updates are grouped: after a burst of changes, Claude Code waits 300 ms and runs the script once. If a new update arrives while the script is still running, the run in progress is cancelled. So a slow script does not pile up, but it can leave the line stale. Keep it fast.
The data you can use
The full list is in the official documentation (linked at the end). These are the fields most people use:
model.display_name: for example "Opus 5".workspace.current_dirandworkspace.project_dir: the current folder, and the folder where Claude Code was started.context_window.used_percentage: how full the context window is. It counts input tokens only, so it can differ slightly from/context.context_window.context_window_sizeis the maximum.rate_limits.five_hour.used_percentageandrate_limits.seven_day.used_percentage: how much of your plan's 5-hour and weekly limits you have used, withresets_atas a Unix timestamp. Only on Pro and Max plans, and only after the first reply in a session.cost.total_cost_usd: an estimate calculated at API list prices. It may differ from your real bill. On a Pro or Max plan, usage counts against your plan limits instead, so treat the number as a measure of session size.cost.total_duration_ms: time since the session started.effort.level:low,medium,high,xhighormax. Absent if the model does not support effort.session_name,session_id,version,vim.mode,pr.number,worktree.name.
Some fields are missing until they apply (rate_limits, vim, pr, worktree), and some are null early in a session (context_window.used_percentage). Always give jq a fallback: // 0 for numbers you do arithmetic with, and // empty for things you only show when they exist.
To see exactly what your version sends, point command at a script that saves its input:
#!/bin/bash
tee /tmp/statusline-input.json | jq -r '.model.display_name'Send one message in a session, then read it with jq . /tmp/statusline-input.json.
A complete example: two lines, colors and limits
The first line shows the model, the folder and the git branch. The second shows a ten-block context bar that turns yellow at 70% and red at 90%, plus your 5-hour limit when your plan reports one.
#!/bin/bash
# Line 1: model, folder, git branch. Line 2: context bar and 5-hour limit.
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)
FIVE=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty' | cut -d. -f1)
GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
# Bar color: green below 70%, yellow from 70%, red from 90%
if [ "$PCT" -ge 90 ]; then COLOR=$RED
elif [ "$PCT" -ge 70 ]; then COLOR=$YELLOW
else COLOR=$GREEN; fi
# Ten blocks, one per 10% of the context window
FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
printf -v FILL "%${FILLED}s"; printf -v PAD "%${EMPTY}s"
BAR="${FILL// /█}${PAD// /░}"
BRANCH=$(git -C "$DIR" branch --show-current 2>/dev/null)
LINE1="[$MODEL] ${DIR##*/}${BRANCH:+ on $BRANCH}"
LINE2="${COLOR}${BAR}${RESET} ${PCT}% context"
[ -n "$FIVE" ] && LINE2="$LINE2 | 5h limit ${FIVE}%"
printf '%b\n' "$LINE1" "$LINE2"In a git project, halfway through a session, it looks like this:
Output
[Opus 5] my-app on main
█████░░░░░ 52% context | 5h limit 23%A few details that save time:
- Colors are ANSI escape codes.
printf '%b'interprets them more reliably thanecho -eacross shells. - Width: the script cannot ask the terminal how wide it is (
tput colsdoes not work in there), because Claude Code captures its output. Read theCOLUMNSandLINESenvironment variables, which Claude Code sets before running the script. - Links: OSC 8 escape sequences make text clickable in terminals that support them, such as iTerm2, Kitty and WezTerm. Terminal.app does not.
- Slow commands:
git statusin a large repository can lag. The official page has an example that caches git results in a file named aftersession_idand refreshes it every few seconds.
Windows
Claude Code runs the command through Git Bash if it is installed, otherwise through PowerShell. Write paths in command with forward slashes. Git Bash eats backslashes, and the line fails without an error. To use a PowerShell script:
{
"statusLine": {
"type": "command",
"command": "powershell -NoProfile -File C:/Users/youruser/.claude/statusline.ps1"
}
}When the line stays blank
- Run the script by hand with test JSON, as in the steps above. It must print to standard output and exit with code 0. A non-zero exit or empty output leaves the line blank.
- Check that it is executable:
chmod +x ~/.claude/statusline.sh. - Check that
jqis installed and on thePATH. - If the folder has not been trusted yet, the status line does not run. Restart Claude Code and accept the trust dialog.
disableAllHooksset totruein your settings also turns the status line off. In a company setup,allowManagedHooksOnlyin managed settings allows only a status line defined by your administrator.- Start with
claude --debug. It logs the exit code and the error output of the first status line run in the session. - Notifications (MCP errors, update notices, the low-context warning) share the status line's row and can cut it short on a narrow terminal.
Sources
- Customize your status line, Claude Code documentation
- Settings, Claude Code documentation
- jq
Checked on
Claude Code 2.1.273 on AlmaLinux 9.8, with jq 1.6 and Bash 5.1. The example script was run in a real session, and the field list was compared with the JSON that session sent.