Your Claude Code terminal is missing crucial information. Every time you glance at your screen, you're left wondering: which model am I using? How much have I spent? What branch am I on?
The status line solves this problem by putting everything you need right at the bottom of your interface — model name, git branch, session cost, and context usage — all updated in real time.
Think of it like customizing your shell prompt with Oh-my-zsh or Starship. One line of information that keeps you oriented while you work, without breaking your flow.

What the Status Line Shows
The status line sits at the bottom of Claude Code and refreshes automatically whenever your conversation changes. It displays anything your script outputs: the current model, your git branch, how much you've spent this session, or how close you are to hitting your context window limit.
Here's what a properly configured status line looks like:
[Opus] my-project | main | $0.42 | Context: 37%That single line tells you everything: the model (Opus), your project folder, the git branch (main), your session cost ($0.42), and how much of your context window you've used (37%). All of it updates automatically as you work.
Quick Setup with /statusline
The fastest way to get started is using the built-in /statusline command. Just type it directly into Claude Code:
/statuslineClaude Code generates a script that mirrors your terminal prompt by default. But you can also give it specific instructions:
/statusline show the model name in orange
/statusline display git branch and session cost
/statusline show context window percentage with color codingThat's it. Claude Code writes the script, configures everything, and your status line appears instantly. If you want more control over the details, keep reading for manual setup.
Manual Setup via settings.json
For complete control, add a statusLine entry to your project's .claude/settings.json (or your global ~/.claude/settings.json to apply it across all projects):
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}Three fields you need to know:
- 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)
After adding this configuration, create the script file and make it executable:
touch ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.shThe chmod +x step is critical. If your status line doesn't appear, missing execute permission is almost always the culprit.
How the Status Line Engine Works
Understanding the mechanics helps when you're debugging or building custom scripts:
- 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 point is key. Your script receives structured JSON containing the current model, workspace paths, session cost, context window stats, and more. You parse it, format it, and print one line to stdout.
The JSON Input Your Script Receives
Every time the status line updates, your script gets this JSON structure via 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 matter most for practical status lines:
model.display_name: Short model name like "Opus" or "Sonnet"workspace.current_dir: Where you're working right nowcost.total_cost_usd: Running cost of the session in dollarscost.total_lines_added / total_lines_removed: Track code changescontext_window.used_percentage: Pre-calculated context usage (0-100)context_window.context_window_size: Total context window capacity
Status Line Scripts You Can Copy
Here are complete, runnable scripts in multiple languages. Pick the one that fits your workflow.
Simple Bash Status Line
The simplest useful status line shows model name and 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
This script uses jq for JSON parsing. If you don't have it installed, run brew install jq on macOS or sudo apt install jq on Ubuntu.
Git-Aware Bash Status Line
Adding the current git branch is useful 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
Get model, git branch, cost, and context percentage all in 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-coded output makes information easier to scan 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}"The context percentage turns green below 50%, yellow between 50–80%, and red above 80%. You can immediately see when it's time to manage your context window.
Helper Function Approach for Complex Scripts
When your status line script grows, helper functions keep things 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
The +156/-23 shows lines added and removed in the session—a quick way to gauge how much code has changed.
Python Status Line
If you prefer Python 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
Watching your context window is one of the most practical uses for the status line. When context fills up, Claude Code compacts the conversation and you lose detail. Knowing where you stand helps you decide when to start fresh or compact strategically.
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 manual approach lets you see raw token numbers alongside the percentage. This is especially useful when you want to know exactly how many tokens you have left, particularly when choosing between different models with different context sizes.
Tracking Session Cost
The cost.total_cost_usd field updates in real time. Displaying it in your status line keeps spending visible without needing to check 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}"If you're on a budget or tracking costs per feature, this becomes immediately useful. Pair it with model selection strategies to switch models when a task doesn't need the most expensive option.
Troubleshooting
Status line doesn't appear at all
The most common cause is missing execute permission on the script file. Fix it with:
chmod +x ~/.claude/statusline.shScript runs but output is empty
Your script might be writing to stderr instead of stdout. The status line only reads the first line of stdout. Add a simple echo "test" to verify output, then build from there.
Testing your script manually
You can test without running Claude Code by piping mock JSON into your script:
echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/test"},"cost":{"total_cost_usd":0.5},"context_window":{"used_percentage":25}}' | ~/.claude/statusline.shIf that prints the expected output, the script works. If it doesn't, the issue is in your parsing logic.
jq not found
Install it with your package manager:
# macOS
brew install jq
# Ubuntu/Debian
sudo apt install jq
# Windows (via scoop)
scoop install jqCreative Status Line Ideas
Once you have the basics working, here are some ideas to make your status line even more useful:
- Lines changed tracker: Show +added/-removed to monitor session productivity
- Session duration: Calculate elapsed time from
total_duration_ms - Model ID display: Show the full model identifier when testing different configurations
- Project vs current directory: Show both when Claude Code navigates into subdirectories
- Cost-per-minute: Divide
total_cost_usdbytotal_duration_msto see your burn rate - Context window bar: Replace the percentage with a visual bar like
[========--] - Conditional warnings: Flash a color when context exceeds 80% or cost passes a threshold
The status line runs a script you control. If you can write it in bash, Python, or Node.js, you can display it.
Final Thoughts
The status line transforms Claude Code from a black box into a transparent workspace. You know what you're spending, which model you're using, and when you're running out of context — all without breaking your flow.
Start with the /statusline command for quick setup, or dive into the manual configuration if you want full control. Either way, you'll have the information you need right where you need it: at the bottom of your screen, updating automatically as you work.