Claude Code Status Line Setup
How to wire up a custom Claude Code status line. Configuration, JSON input, ready-to-copy scripts in bash, Python, and Node.js.
Problem: The Claude Code terminal leaves a lot of useful information off the screen. A custom status line drops the model name, the git branch, the running session cost, and your context usage right under the chat interface.
Think of it as PS1 for Claude Code. If you've ever tweaked a shell prompt with Oh-my-zsh or Starship, the idea is identical. One line of live info that keeps you oriented while you work.
What the Status Line Shows
The status line lives at the bottom of the Claude Code interface and refreshes every time the conversation changes. Whatever your script prints gets displayed: the current model, the active git branch, how much the session has cost, or how full your context window is.
Here's a configured one in the wild:
[Opus] my-project | main | $0.42 | Context: 37%
One line. Model, project folder, git branch, session cost so far, and the percentage of the context window in use. Every field updates on its own.
Quick Setup with /statusline
Built right into the CLI is the /statusline command. Type it inside Claude Code and it generates a script for you.
/statusline
The default behavior creates a status line script that mirrors your terminal prompt. You can also hand it a specific request:
/statusline show the model name in orange
/statusline display git branch and session cost
/statusline show context window percentage with color coding
That's it. Claude Code writes the script, wires up the settings, and the status line appears. For full control, the manual route is next.
Manual Setup via settings.json
To configure it by hand, add a statusLine entry to your project's .claude/settings.json (or to your global ~/.claude/settings.json if you want it on every project):
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}Three fields worth knowing:
- type: Always
"command"for script-based status lines - command: Path to the script that generates your status line output
- padding: Number of empty lines above the status line (0 is typical)
Once that entry is in place, create the script file and mark it executable:
touch ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.shThe chmod +x step matters. If your status line never shows up, a missing execute permission is almost always the cause.
How the Status Line Engine Works
Knowing the mechanics pays off the moment you debug or customize a script:
- Update trigger: The status line refreshes whenever conversation messages change
- Throttle: Updates run at most every 300ms to avoid performance issues
- Output handling: Only the first line of stdout from your script becomes the status line text
- Colors: Full ANSI color code support for styling
- Input: Claude Code pipes a JSON object with session data into your script via stdin
That last bullet is the key. Your script gets a structured JSON payload on stdin containing the current model, workspace paths, session cost, context window stats, and more. Parse it, format it, print one line to stdout.
The JSON Input Your Script Receives
Every status line update, your script receives this JSON structure on stdin:
{
"hook_event_name": "Status",
"session_id": "abc123...",
"cwd": "/current/working/directory",
"model": {
"id": "claude-opus-4-1",
"display_name": "Opus"
},
"workspace": {
"current_dir": "/current/working/directory",
"project_dir": "/original/project/directory"
},
"version": "1.0.80",
"cost": {
"total_cost_usd": 0.01234,
"total_duration_ms": 45000,
"total_api_duration_ms": 2300,
"total_lines_added": 156,
"total_lines_removed": 23
},
"context_window": {
"total_input_tokens": 15234,
"total_output_tokens": 4521,
"context_window_size": 200000,
"used_percentage": 42.5,
"remaining_percentage": 57.5,
"current_usage": {
"input_tokens": 8500,
"output_tokens": 1200,
"cache_creation_input_tokens": 5000,
"cache_read_input_tokens": 2000
}
}
}The fields that do the real work in a practical status line:
- model.display_name: Short model name like "Opus" or "Sonnet"
- workspace.current_dir: Where you're working right now
- cost.total_cost_usd: Running cost of the session in dollars
- cost.total_lines_added / total_lines_removed: Track code changes
- context_window.used_percentage: Pre-calculated context usage (0-100)
- context_window.context_window_size: Total context window capacity
Status Line Scripts You Can Copy
Below are complete, runnable scripts in several languages. Grab the one that fits your setup.
Simple Bash Status Line
The smallest status line worth running. Model name plus current directory:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
echo "[$MODEL] ${CURRENT_DIR##*/}"Output: [Opus] my-project
Behind the scenes this script leans on jq for JSON parsing. If jq isn't installed, run brew install jq on macOS or sudo apt install jq on Ubuntu.
Git-Aware Bash Status Line
Branch information joins the line, which pays off when you're juggling feature branches:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
GIT_BRANCH=""
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
if [ -n "$BRANCH" ]; then
GIT_BRANCH=" | $BRANCH"
fi
fi
echo "[$MODEL] ${CURRENT_DIR##*/}$GIT_BRANCH"Output: [Opus] my-project | feature/auth
Full-Featured Bash Status Line
Model, git branch, session cost, and context percentage, all on one line:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
PERCENT_USED=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
# Git branch
GIT_BRANCH=""
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
if [ -n "$BRANCH" ]; then
GIT_BRANCH=" | $BRANCH"
fi
fi
# Format cost to 2 decimal places
COST_FMT=$(printf '%.2f' "$COST")
# Round context percentage
PERCENT_INT=$(printf '%.0f' "$PERCENT_USED")
echo "[$MODEL] ${CURRENT_DIR##*/}$GIT_BRANCH | \$${COST_FMT} | Ctx: ${PERCENT_INT}%"Output: [Opus] my-project | main | $0.42 | Ctx: 37%
Bash with ANSI Colors
Color coding makes the line scannable at a glance:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
PERCENT_USED=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
# ANSI colors
ORANGE='\033[38;5;208m'
GREEN='\033[32m'
YELLOW='\033[33m'
RED='\033[31m'
CYAN='\033[36m'
RESET='\033[0m'
# Color context percentage based on usage
PERCENT_INT=$(printf '%.0f' "$PERCENT_USED")
if [ "$PERCENT_INT" -lt 50 ]; then
CTX_COLOR=$GREEN
elif [ "$PERCENT_INT" -lt 80 ]; then
CTX_COLOR=$YELLOW
else
CTX_COLOR=$RED
fi
COST_FMT=$(printf '%.2f' "$COST")
echo -e "${ORANGE}[$MODEL]${RESET} ${CURRENT_DIR##*/} ${CYAN}\$${COST_FMT}${RESET} ${CTX_COLOR}Ctx:${PERCENT_INT}%${RESET}"Below 50%, the context percentage is green. Between 50-80%, yellow. Above 80%, red. A quick visual signal that it's time to manage the context window.
Helper Function Approach for Complex Scripts
As a status line script grows, helper functions keep it readable:
#!/bin/bash
input=$(cat)
# Helper functions for clean extraction
get_model() { echo "$input" | jq -r '.model.display_name'; }
get_dir() { echo "$input" | jq -r '.workspace.current_dir'; }
get_cost() { echo "$input" | jq -r '.cost.total_cost_usd // 0'; }
get_context() { echo "$input" | jq -r '.context_window.used_percentage // 0'; }
get_added() { echo "$input" | jq -r '.cost.total_lines_added // 0'; }
get_removed() { echo "$input" | jq -r '.cost.total_lines_removed // 0'; }
get_version() { echo "$input" | jq -r '.version'; }
MODEL=$(get_model)
DIR=$(get_dir)
COST=$(printf '%.2f' "$(get_cost)")
CTX=$(printf '%.0f' "$(get_context)")
ADDED=$(get_added)
REMOVED=$(get_removed)
echo "[$MODEL] ${DIR##*/} | \$$COST | Ctx:${CTX}% | +$ADDED/-$REMOVED"Output: [Opus] my-project | $0.42 | Ctx:37% | +156/-23
+156/-23 at the end tracks lines added and removed in the session. A quick read on how much code has actually changed.
Python Status Line
For Python users over bash:
#!/usr/bin/env python3
import json
import sys
import os
import subprocess
data = json.load(sys.stdin)
model = data["model"]["display_name"]
current_dir = os.path.basename(data["workspace"]["current_dir"])
cost = data.get("cost", {}).get("total_cost_usd", 0)
ctx_pct = data.get("context_window", {}).get("used_percentage", 0)
# Get git branch
git_branch = ""
try:
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True, text=True, timeout=2
)
if result.returncode == 0 and result.stdout.strip():
git_branch = f" | {result.stdout.strip()}"
except Exception:
pass
print(f"[{model}] {current_dir}{git_branch} | ${cost:.2f} | Ctx:{ctx_pct:.0f}%")Node.js Status Line
For JavaScript developers:
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
let input = "";
process.stdin.on("data", (chunk) => (input += chunk));
process.stdin.on("end", () => {
const data = JSON.parse(input);
const model = data.model.display_name;
const currentDir = path.basename(data.workspace.current_dir);
const cost = (data.cost?.total_cost_usd || 0).toFixed(2);
const ctxPct = Math.round(data.context_window?.used_percentage || 0);
// Get git branch
let gitBranch = "";
try {
const branch = execSync("git branch --show-current", {
encoding: "utf8",
timeout: 2000,
}).trim();
if (branch) gitBranch = ` | ${branch}`;
} catch (e) {}
console.log(
`[${model}] ${currentDir}${gitBranch} | $${cost} | Ctx:${ctxPct}%`,
);
});Tracking Context Window Usage
Keeping eyes on the context window is one of the most practical reasons to run a status line. When it fills up, Claude Code compacts the conversation and detail gets lost. Seeing where you stand helps you decide when to start fresh or compact on your own terms.
Simple approach using the pre-calculated percentage:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
PERCENT_USED=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
echo "[$MODEL] Context: ${PERCENT_USED}%"Advanced approach with manual calculation from raw token counts:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size')
USAGE=$(echo "$input" | jq '.context_window.current_usage')
if [ "$USAGE" != "null" ]; then
CURRENT_TOKENS=$(echo "$USAGE" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
PERCENT_USED=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE))
echo "[$MODEL] Context: ${PERCENT_USED}% (${CURRENT_TOKENS}/${CONTEXT_SIZE} tokens)"
else
echo "[$MODEL] Context: 0%"
fiThe advanced version exposes the raw token numbers alongside the percentage. Useful when you want to know the exact remaining token budget, especially while picking between models with different context sizes.
Tracking Session Cost
cost.total_cost_usd updates in real time. Showing it in the status line keeps spending visible without a trip to the dashboard:
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
COST_FMT=$(printf '%.2f' "$COST")
echo "[$MODEL] Session: \$${COST_FMT}"For anyone on a budget or tracking cost per feature, that visibility pays off right away. Pair it with model selection strategies and you'll swap models as soon as a task drops below Opus-level demands.
Troubleshooting
Status line doesn't appear at all
A missing execute permission on the script file is the usual culprit. Fix it with:
chmod +x ~/.claude/statusline.sh
Script runs but output is empty
Your script may be writing to stderr instead of stdout. Only the first line of stdout reaches the status line. Add a quick echo "test" to confirm output, then build from there.
Testing your script manually
You can test without launching Claude Code by piping mock JSON straight into the script:
echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/test"},"cost":{"total_cost_usd":0.5},"context_window":{"used_percentage":25}}' | ~/.claude/statusline.sh
If that prints the expected line, the script is fine. If nothing comes out, the problem is in your parsing.
jq not found
Grab it from your package manager:
# macOS
brew install jq
# Ubuntu/Debian
sudo apt install jq
# Windows (via scoop)
scoop install jqCreative Status Line Ideas
Once the basics run, a few ideas to push the status line further:
- Lines changed tracker: Print
+added/-removedto keep an eye on session productivity - Session duration: Derive elapsed time from
total_duration_ms - Model ID display: Surface the full model identifier while testing different model configurations
- Project vs current directory: Show both when Claude Code moves into subdirectories
- Cost-per-minute: Divide
total_cost_usdbytotal_duration_msand see the real burn rate - Context window bar: Swap the percentage for a visual bar like
[========--] - Conditional warnings: Flip colors when context crosses 80% or cost passes a threshold
The status line runs any script you give it. Bash, Python, Node.js, anything you can write will render on screen.
What to Try Next
Your status line is live. Natural next moves:
- Manage context proactively: Put the context percentage to work alongside context buffer management strategies
- Set up the terminal environment: Learn terminal control techniques for a complete Claude Code workspace
- Configure project settings: Lock in your configuration basics so every session starts with the right context
Stop configuring. Start building.