A complete guide to building a Claude Code status line that shows your 5-hour and weekly usage limits with color-coded pacing, reset times, and context window usage — all from data Claude Code already provides via stdin.
What you get:
your-project │ ctx 0% │ 5hr (3pm) 16/65% │ wk (fri,12pm) 44/57% │ Opus 4.6 (1M context)
your-project— current working directory namectx 0%— context window usage5hr (3pm) 16/65%— 5-hour window: 16% used, 65% of window elapsed (pace target), resets at 3pmwk (fri,12pm) 44/57%— 7-day window: 44% used, 57% of window elapsed, resets Friday at 12pm- Color-coded by pacing: green (under pace), yellow (at/slightly over), red (>10% over pace)
- Pace target shown in hot pink after the
/
- macOS (uses BSD
date— Linux adaptation notes at the bottom) jqinstalled (brew install jq)- Claude Code v2.1+ (must provide
rate_limitsin status line stdin JSON)
Claude Code pipes a JSON object to your status line script via stdin on every render. As of v2.1+, this JSON includes a rate_limits field with your current usage percentages and reset timestamps — no API calls, OAuth tokens, or caching needed.
Claude Code provides these fields (among others):
{
"model": { "display_name": "Opus 4.6 (1M context)" },
"workspace": { "current_dir": "/Users/you/project" },
"context_window": { "used_percentage": 12.5 },
"rate_limits": {
"five_hour": {
"used_percentage": 16.0,
"resets_at": 1743973200
},
"seven_day": {
"used_percentage": 44.0,
"resets_at": 1744470000
}
}
}used_percentage— how much of your limit you've consumed (0–100)resets_at— Unix epoch when the window resets
The "pace" number tells you what percentage of the window's time has elapsed — i.e., where you'd be if you spread usage evenly across the entire window. If your usage is below the pace, you're in good shape.
window_start = resets_at - window_duration
elapsed = now - window_start
pace = elapsed / window_duration × 100
For the 5-hour window: window_duration = 18,000 seconds
For the 7-day window: window_duration = 604,800 seconds
Colors compare your actual usage against the pace target, not absolute thresholds:
| Condition | Color | Meaning |
|---|---|---|
| usage < pace | Green | Under pace — you're fine |
| usage ≥ pace AND usage ≤ pace × 1.1 | Yellow | At pace or slightly over (within 10%) |
| usage > pace × 1.1 | Red | More than 10% over pace — slow down |
This is more useful than fixed thresholds because 60% usage is fine if 80% of the window has passed, but alarming if only 20% has.
Save this as ~/.claude/statusline-command.sh:
#!/bin/bash
# Read Claude Code context from stdin
input=$(cat)
# Extract information from Claude Code context
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
current_dir=$(echo "$input" | jq -r '.workspace.current_dir // ""')
context_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# Basename of current directory (p10k Pure \W style: blue folder name)
if [ -n "$current_dir" ]; then
dir_display=$(basename "$current_dir")
else
dir_display=$(basename "$HOME")
fi
# Get git status if we're in a git repo
# Green = clean, bright yellow = uncommitted changes (staged, unstaged, or untracked)
git_info=""
git_color=""
repo_dir="${current_dir:-$HOME}"
if git -C "$repo_dir" --no-optional-locks rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git -C "$repo_dir" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null || git -C "$repo_dir" --no-optional-locks rev-parse --short HEAD 2>/dev/null)
if [ -n "$branch" ]; then
if ! git -C "$repo_dir" --no-optional-locks diff --quiet 2>/dev/null || \
! git -C "$repo_dir" --no-optional-locks diff --cached --quiet 2>/dev/null || \
[ -n "$(git -C "$repo_dir" --no-optional-locks ls-files --others --exclude-standard 2>/dev/null)" ]; then
git_color="\033[93m" # bright yellow — dirty
git_info=" ${branch}*"
else
git_color="\033[32m" # green — clean
git_info=" ${branch}✓"
fi
fi
fi
color_for_pct() {
local pct=$1
if [ "$pct" -ge 80 ]; then
printf "\033[91m" # bright red
elif [ "$pct" -ge 50 ]; then
printf "\033[33m" # yellow
else
printf "\033[32m" # green
fi
}
CTX_COLOR=$(color_for_pct "$context_pct")
# --- Usage limits from Claude Code's rate_limits field ---
usage_5h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty' 2>/dev/null | cut -d. -f1)
usage_7d=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty' 2>/dev/null | cut -d. -f1)
# Calculate pacing targets using resets_at from the rate_limits data
NOW_EPOCH=$(date +%s)
target_5h=""
target_7d=""
resets_5h_label=""
resets_7d_label=""
if [ -n "$usage_5h" ]; then
resets_5h=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty' 2>/dev/null)
if [ -n "$resets_5h" ]; then
reset_epoch=$resets_5h
if [ -n "$reset_epoch" ]; then
window_secs=$((5 * 3600))
start_epoch=$((reset_epoch - window_secs))
elapsed=$((NOW_EPOCH - start_epoch))
[ "$elapsed" -lt 0 ] && elapsed=0
[ "$elapsed" -gt "$window_secs" ] && elapsed=$window_secs
target_5h=$((elapsed * 100 / window_secs))
resets_5h_label=$(date -d "@${reset_epoch}" '+%I%p' 2>/dev/null | sed 's/^0//' | tr '[:upper:]' '[:lower:]')
# Fallback for macOS
[ -z "$resets_5h_label" ] && resets_5h_label=$(date -r "$reset_epoch" '+%-I%p' 2>/dev/null | tr '[:upper:]' '[:lower:]')
fi
fi
fi
if [ -n "$usage_7d" ]; then
resets_7d=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty' 2>/dev/null)
if [ -n "$resets_7d" ]; then
reset_epoch=$resets_7d
if [ -n "$reset_epoch" ]; then
window_secs=$((7 * 86400))
start_epoch=$((reset_epoch - window_secs))
elapsed=$((NOW_EPOCH - start_epoch))
[ "$elapsed" -lt 0 ] && elapsed=0
[ "$elapsed" -gt "$window_secs" ] && elapsed=$window_secs
target_7d=$((elapsed * 100 / window_secs))
resets_7d_label=$(date -d "@${reset_epoch}" '+%a,%-I%p' 2>/dev/null | tr '[:upper:]' '[:lower:]')
# Fallback for macOS
[ -z "$resets_7d_label" ] && resets_7d_label=$(date -r "$reset_epoch" '+%a,%-I%p' 2>/dev/null | tr '[:upper:]' '[:lower:]')
fi
fi
fi
# Color actual usage vs pace (percentage-based threshold, integer math):
# green — usage < pace
# yellow — usage >= pace AND usage * 100 <= pace * 110 (within 10% over pace)
# red — usage * 100 > pace * 110 (more than 10% over pace)
color_for_usage_vs_pace() {
local usage=$1 pace=$2
if [ -n "$pace" ] && [ $((usage * 100)) -gt $((pace * 110)) ]; then
printf "\033[91m" # red — more than 10% over pace
elif [ -n "$pace" ] && [ "$usage" -ge "$pace" ]; then
printf "\033[33m" # yellow — over pace but within 10%
else
printf "\033[32m" # green — under pace
fi
}
# Build usage parts — compact numeric format: "5hr (12pm) 30/50%"
usage_parts=""
if [ -n "$usage_5h" ]; then
U5_COLOR=$(color_for_usage_vs_pace "$usage_5h" "$target_5h")
reset_label=""
[ -n "$resets_5h_label" ] && reset_label="(${resets_5h_label}) "
pace_part=""
[ -n "$target_5h" ] && pace_part="\033[38;5;199m/${target_5h}"
usage_parts="${U5_COLOR}5hr ${reset_label}${usage_5h}${pace_part}%\033[0m"
fi
if [ -n "$usage_7d" ]; then
U7_COLOR=$(color_for_usage_vs_pace "$usage_7d" "$target_7d")
reset_7d_label_str=""
[ -n "$resets_7d_label" ] && reset_7d_label_str="(${resets_7d_label}) "
pace_part_7d=""
[ -n "$target_7d" ] && pace_part_7d="\033[38;5;199m/${target_7d}"
[ -n "$usage_parts" ] && usage_parts="${usage_parts}\033[2m │ \033[0m"
usage_parts="${usage_parts}${U7_COLOR}wk ${reset_7d_label_str}${usage_7d}${pace_part_7d}%\033[0m"
fi
# Single line output
# blue(57C7FF) dir · green/yellow git branch · ctx% · usage parts · model
line="\033[38;2;87;199;255m${dir_display}\033[0m${git_color}${git_info}\033[0m\033[2m │ ${CTX_COLOR}ctx ${context_pct}%\033[0m"
if [ -n "$usage_parts" ]; then
line="${line}\033[2m │ \033[0m${usage_parts}"
fi
line="${line}\033[2m │ ${model_name}\033[0m"
printf "%b\n" "$line"chmod +x ~/.claude/statusline-command.shAdd this to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-command.sh"
}
}# Test with mock data that simulates what Claude Code sends
echo '{
"model": {"display_name": "Opus 4.6 (1M context)"},
"workspace": {"current_dir": "/tmp/test-project"},
"context_window": {"used_percentage": 20},
"rate_limits": {
"five_hour": {"used_percentage": 37, "resets_at": '"$(($(date +%s) + 7200))"'},
"seven_day": {"used_percentage": 26, "resets_at": '"$(($(date +%s) + 259200))"'}
}
}' | bash ~/.claude/statusline-command.shyour-project │ ctx 0% │ 5hr (3pm) 16/65% │ wk (fri,12pm) 44/57% │ Opus 4.6 (1M context)
Reading 5hr (3pm) 16/65%:
- 5hr — the 5-hour rolling window
- (3pm) — window resets at 3pm local time
- 16 — you've used 16% of your 5-hour limit
- /65 — 65% of the 5-hour window has elapsed (the pace target, shown in hot pink)
- % — both numbers are percentages
If usage (16) < pace (65), you're well under pace → green. You could use 4× your current rate and still not hit the limit before the window resets.
| Element | Current | ANSI Code |
|---|---|---|
| Directory | Blue (#57C7FF) | \033[38;2;87;199;255m |
| Git clean | Green | \033[32m |
| Git dirty | Bright yellow | \033[93m |
| Pace target number | Hot pink | \033[38;5;199m |
| Under pace | Green | \033[32m |
| At pace (within 10%) | Yellow | \033[33m |
| Over pace (>10%) | Bright red | \033[91m |
| Context <50% | Green | \033[32m |
| Context 50-80% | Yellow | \033[33m |
| Context >80% | Bright red | \033[91m |
Note: Dark red (\033[31m) is nearly invisible on dark terminal backgrounds. Use bright red (\033[91m) instead.
Claude Code pipes these fields to your script via stdin:
| Field | Description |
|---|---|
model.display_name |
Current model name |
model.id |
Model identifier |
context_window.used_percentage |
Context window usage |
context_window.total_input_tokens |
Cumulative input tokens |
context_window.total_output_tokens |
Cumulative output tokens |
rate_limits.five_hour.used_percentage |
5-hour window usage (0–100) |
rate_limits.five_hour.resets_at |
5-hour reset time (Unix epoch) |
rate_limits.seven_day.used_percentage |
7-day window usage (0–100) |
rate_limits.seven_day.resets_at |
7-day reset time (Unix epoch) |
cost.total_cost_usd |
Session cost |
cost.total_duration_ms |
Total session time |
cost.total_lines_added |
Lines added |
cost.total_lines_removed |
Lines removed |
workspace.current_dir |
Current directory |
output_style.name |
Output style |
vim.mode |
Vim mode (if enabled) |
The script tries Linux-style date -d first with macOS date -r as fallback. If you're Linux-only, you can drop the fallback lines. The only OS-specific part is date formatting:
- macOS:
date -r $epoch '+%-I%p'(convert epoch to human time) - Linux:
date -d "@$epoch" '+%I%p' | sed 's/^0//'(same thing)
No keychain, no curl, no platform-specific credential storage needed.
# 1. Is jq installed?
which jq && jq --version || echo "MISSING: brew install jq"
# 2. Does the script run without errors?
echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/tmp"},"context_window":{"used_percentage":42},"rate_limits":{"five_hour":{"used_percentage":30,"resets_at":'"$(($(date +%s)+3600))"'},"seven_day":{"used_percentage":50,"resets_at":'"$(($(date +%s)+86400))"'}}}' \
| bash ~/.claude/statusline-command.sh 2>&1
echo "exit: $?"
# 3. Does Claude Code provide rate_limits?
# Add this temporarily to the top of your script to capture the JSON:
# cat > /tmp/cc-statusline-debug.json
# Then check: jq '.rate_limits' /tmp/cc-statusline-debug.json| Problem | Cause | Fix |
|---|---|---|
| No status line at all | Script exits non-zero | Run the diagnostic above — check stderr |
| Usage shows but no pace numbers | resets_at missing from rate_limits |
Update Claude Code — older versions may not include it |
| Only ctx shows, no 5hr/wk | rate_limits not in stdin JSON |
Update Claude Code to v2.1+ |
| Reset time label is blank | date format incompatible |
Check if you're on macOS or Linux — the script tries both |
| Colors look wrong | Terminal doesn't support 256-color | Most modern terminals do; check echo $TERM |
| Git branch slow in large repos | git diff and git ls-files scanning |
The --no-optional-locks flag helps; consider caching git info |
jq: command not found |
jq not installed | brew install jq |
This approach replaced an earlier version that called Anthropic's undocumented /api/oauth/usage endpoint with an OAuth token from the macOS Keychain. That worked but required managing credentials, handling token refresh, caching API responses, and dealing with keychain staleness. Claude Code v2.1+ exposes rate_limits directly in the status line stdin JSON, eliminating all of that complexity.
- Original usage API approach inspired by codelynx.dev
- Context window calculation inspired by Richard-Weiss/ffadccca6238
Great work. These are replacement lines to make it work in Linux (and in theory WSL):