A complete guide to building a Claude Code status line that shows your 5-hour and weekly usage limits with color-coded progress bars, pacing markers, reset times, and context window usage.
What you get:
yearone-3 main* │ Opus 4.6 │ 07:00 PM │ ctx ▓▓░░░░░░░░ 20% │ 5hr (11pm) ▓▓▓│░░░░░░ 37% │ wk (thu, 10am) ▓▓░│░░░░░░ 26%
- Color-coded bars: green (<50%), yellow (50-80%), bright red (>80%)
- Hot pink
│pacing marker showing where you should be for even usage across the window - Reset times so you know when your limits refill
- macOS (uses
securitykeychain CLI and BSDdate) (linux uses a plain file, no requirements) jqinstalled (brew install jq)- Claude Code with an active subscription (Max, Pro, etc.)
Save the file below as ~/.claude/statusline-command.sh:
chmod +x ~/.claude/statusline-command.shAdd this to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-command.sh"
}
}# Check if you have a valid token
security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq '.claudeAiOauth.scopes'You need ["user:inference", "user:profile"]. If the entry is missing, expired, or has wrong scopes:
# Delete stale entry
security delete-generic-password -s "Claude Code-credentials"
# Quit ALL Claude Code instances, then restart.
# CC opens a browser for OAuth — this creates a fresh keychain entry with correct scopes.Ensure that ~/.claude/.credentials.json is populated
# Test the script manually
echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/tmp"},"context_window":{"used_percentage":42}}' \
| bash ~/.claude/statusline-command.shThe script uses ANSI 256-color codes. Change these to taste:
| Element | Current | Code |
|---|---|---|
| Directory | Light cyan | \033[96m |
| Pacing marker | Hot pink | \033[38;5;199m |
| Good (<50%) | Green | \033[32m |
| Warning (50-80%) | Yellow | \033[33m |
| Danger (>80%) | Bright red | \033[91m |
Dark red (\033[31m) is nearly invisible on dark terminal backgrounds. Use bright red (\033[91m) instead.
Add a width argument to make_bar() to make bars wider or narrower than 10 characters
Change USAGE_CACHE_AGE=60 to control how often the API is called (in seconds).
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 |
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) |
Run this to check everything at once:
#!/usr/bin/bash
echo "=== 1. jq installed? ==="
which jq && jq --version || echo "MISSING: brew install jq"
echo -e "\n=== 2. Credentials found? ==="
CREDS=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) # macos
[ -z "$CREDS" ] && CREDS=$(<~/.claude/.credentials.json) # linux, wsl
if [ -z "$CREDS" ]; then
echo "MISSING: No keychain entry or credentials file. Quit all CC instances and restart to trigger OAuth login."
else
echo "Found keychain entry"
fi
echo -e "\n=== 3. Has claudeAiOauth? ==="
echo "$CREDS" | jq -e '.claudeAiOauth' >/dev/null 2>&1 \
&& echo "YES" \
|| echo "NO — keychain only has: $(echo "$CREDS" | jq -r 'keys | join(", ")'). Delete entry and restart CC."
echo -e "\n=== 4. Token scopes ==="
echo "$CREDS" | jq -r '.claudeAiOauth.scopes // ["none"] | join(", ")' 2>/dev/null
echo "(need: user:inference, user:profile)"
echo -e "\n=== 5. Token expired? ==="
EXPIRES=$(echo "$CREDS" | jq -r '.claudeAiOauth.expiresAt // 0' 2>/dev/null)
NOW_MS=$(($(date +%s) * 1000))
echo $EXPIRES
if [ "$EXPIRES" -gt "$NOW_MS" ] 2>/dev/null; then
echo "VALID — expires $(date -d @$((EXPIRES / 1000)))"
else
echo "EXPIRED — delete keychain entry and restart CC"
fi
echo -e "\n=== 6. API test ==="
TOKEN=$(echo "$CREDS" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$TOKEN" ]; then
RESP=$(curl -s --max-time 5 "https://api.anthropic.com/api/oauth/usage" \
-H "Authorization: Bearer $TOKEN" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "Content-Type: application/json")
if echo "$RESP" | jq -e '.five_hour' >/dev/null 2>&1; then
echo "SUCCESS"
echo "$RESP" | jq '{five_hour: .five_hour.utilization, seven_day: .seven_day.utilization}'
else
echo "FAILED: $(echo "$RESP" | jq -r '.error.message // "unknown error"')"
fi
else
echo "SKIPPED — no token"
fi
echo -e "\n=== 7. Script test ==="
echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/tmp"},"context_window":{"used_percentage":42}}' \
| bash ./statusline-command.sh 2>&1 && echo -e "\n(exit: 0)" || echo -e "\n(exit: $?)"| Problem | Cause | Fix |
|---|---|---|
| No status line at all | Script crashes with non-zero exit | Run the diagnostic above — step 7 shows the error |
| No usage data (only ctx shows) | Keychain token expired or missing | Run diagnostic steps 2-6, then delete entry + restart CC |
| Usage shows 0% with bar at far left | Stale cache from a previous window | rm /tmp/claude-statusline-usage.json to force a fresh fetch |
| Pacing marker at far left (wrong) | UTC timezone not handled in date parsing | Ensure -u flag: date -juf not date -jf |
printf: invalid format character |
ANSI escape codes in printf variable | Use echo -e for final output, not printf |
Token scope error (user:profile) |
Used claude setup-token |
That token only has user:inference. Delete keychain entry, restart CC for browser OAuth |
Keychain has only mcpOAuth key |
Ran /login inside CC |
/login is for MCP servers, not CC auth. Delete entry, restart CC |
| Status line wraps to next line | Output too wide for terminal | Shorten labels (wk not weekly), drop user@host, drop seconds from time |
jq: command not found |
jq not installed | brew install jq |
| Keychain access popup/prompt | macOS asking permission | Click "Always Allow" — the script reads keychain on every cache refresh |
| Usage data is stale / not updating | Cache file not refreshing | Check ls -la /tmp/claude-statusline-usage.json — if mod time is old, token is probably expired |
| Script works manually but not in CC | disableAllHooks: true in settings |
Remove that setting from ~/.claude/settings.json |
| Git branch slow in large repos | git diff and git ls-files scanning |
Cache git info to a file with a 5-second TTL |
If nothing works, start completely fresh:
# 1. Delete keychain entry (MacOS only)
security delete-generic-password -s "Claude Code-credentials" 2>/dev/null
# 2. Delete stale cache
rm -f /tmp/claude-statusline-usage.json
# 3. Quit ALL Claude Code instances (all terminals)
# Cmd+Q or `killall claude` if desperate
# 4. Restart Claude Code
# It will open a browser for OAuth login
# This creates a fresh keychain entry with user:inference + user:profile scopes
# 5. Verify (MacOS only)
security find-generic-password -s "Claude Code-credentials" -w | jq '.claudeAiOauth.scopes'
# Should output: ["user:inference", "user:profile"]The status line calls an undocumented Anthropic API (/api/oauth/usage) that requires an OAuth token with user:profile scope. Here's where it gets tricky.
Claude Code stores its OAuth credentials in a plain text file: ~/.claude/.credentials.json. Easy.
Claude Code stores OAuth credentials in the macOS Keychain under the service name Claude Code-credentials. The entry contains JSON with a claudeAiOauth object that includes accessToken, refreshToken, expiresAt, and scopes.
Endpoint: GET https://api.anthropic.com/api/oauth/usage
Headers:
Authorization: Bearer <oauth_access_token>
anthropic-beta: oauth-2025-04-20
Content-Type: application/json
Response:
{
"five_hour": {
"utilization": 37.0,
"resets_at": "2026-02-08T04:59:59.000000+00:00"
},
"seven_day": {
"utilization": 26.0,
"resets_at": "2026-02-12T14:59:59.771647+00:00"
},
"seven_day_opus": null,
"seven_day_sonnet": {
"utilization": 1.0,
"resets_at": "2026-02-13T20:59:59.771655+00:00"
},
"extra_usage": {
"is_enabled": false,
"monthly_limit": null,
"used_credits": null,
"utilization": null
}
}utilizationis a percentage (0-100)resets_atis when the window ends (UTC)five_houris a rolling 5-hour windowseven_dayis a rolling 7-day window
- Usage API endpoint discovered via codelynx.dev
- OAuth client ID and refresh flow reverse-engineered from the Claude Code binary
- Context window calculation inspired by Richard-Weiss/ffadccca6238
nice status line setup! this is exactly what I needed, thank you!