Building a Custom Claude Code Statusline
I was watching a YouTube video about someone’s Claude Code setup and noticed their statusline was packed with information - context usage with color-coded bars, plan usage percentages, session timers, weather data, even a memory counter. My statusline was a basic one-liner showing the model name, a monochrome context bar, and a git branch. I wanted some of that, particularly the usage tracking and the visual polish.
What followed was a surprisingly educational detour through the Claude Code ecosystem, three different approaches to usage tracking, and the discovery that the most useful data comes from an API endpoint that barely anyone seems to know about.
Starting with the Basics
The Claude Code statusline is configured through settings.json and runs a shell script that receives JSON on stdin with session data - model info, context window percentage, cost, git status. The output supports ANSI color codes, so you can make it as visual as you want.
The first improvements were straightforward. I replaced the monochrome ▓░ context bar with solid █░ blocks and added color thresholds using the Catppuccin Mocha palette (which matches my terminal theme):
- Green below 50% - plenty of room
- Yellow at 50-79% - getting full
- Red at 80%+ - context compression is coming
I also added the git branch with color coding (red for main, green for develop, mauve for feature branches), session cost, duration, and lines changed. The thinking effort indicator uses three vertical bars matching Claude Code’s own /model picker - blue bars for active effort level, grey for inactive.
None of this required anything beyond the JSON that Claude Code already provides. It’s all in the context_window, cost, model, and cwd fields piped to your statusline script.
The Quest for Usage Data
The piece I really wanted was plan usage - the 5-hour window and weekly utilization percentages. If you type /usage inside Claude Code, you see these numbers. But getting them into a statusline script is a different problem.
Attempt 1: ccusage and Dollar Costs
The first tool I tried was ccusage, an npm package that reads Claude Code’s local JSONL transcript files and calculates costs. It has a blocks --active --json command that returns the current 5-hour window with dollar amounts and projections.
The idea was simple: if you know the cost limit per window for your plan, you can calculate a percentage. The claude-statusline repository - the one from the YouTube video - does exactly this. It estimates Max 5x at $83 per 5-hour window and $500 per week.
The numbers I got: 24% for the 5-hour window, 47% for the weekly. The real numbers from /usage: 40% and 6%.
Not even close. The problem is threefold. The plan limits are guesses - only the Max 20x limits have been verified by the community. The cost accounting from transcript files doesn’t match Anthropic’s internal accounting. And the weekly reset boundary doesn’t necessarily align with ISO weeks.
Attempt 2: Token-Based Counting
The second approach came from claude_monitor_statusline, a Ruby script that reads the same JSONL transcripts but counts tokens and messages instead of dollars. It defines limits like 88k tokens per 5-hour window for Max 5x.
I built a bash version that summed tokens from the last 5 hours across all project transcripts. The raw numbers were interesting - 30k input tokens, 146k output tokens, 3.5M cache creation tokens, 900 messages in one window. But as a percentage of what? The 88k limit is also a guess, and my actual token volumes didn’t line up with it at all.
I showed raw token counts without percentages for a while. Honest, but not very useful at a glance.
The Breakthrough: Anthropic’s OAuth API
Then I found a Python gist by lexfrei that took a completely different approach. Instead of reading local transcripts and guessing limits, it calls Anthropic’s actual usage API at https://api.anthropic.com/api/oauth/usage.
The authentication is elegant - it reads the OAuth token from your macOS Keychain where Claude Code stores its credentials. One HTTP GET request with that token returns the real utilization:
{
"five_hour": {
"utilization": 42.0,
"resets_at": "2026-02-27T18:00:00.408510+00:00"
},
"seven_day": {
"utilization": 7.0,
"resets_at": "2026-03-06T08:00:00.408528+00:00"
}
}
42% five-hour, 7% seven-day. Exactly matching /usage. No guesswork, no estimates, no reverse-engineering from transcript files. Just the actual numbers from Anthropic’s servers.
The same gist also checks status.claude.com for platform health - useful context when things feel slow.
The Final Implementation
The statusline ended up as two files. A bash script (statusline.sh) that handles all the formatting and a Python script (statusline-usage.py) that fetches the API data with caching.
Here’s what the final statusline looks like:

Left to right:
- Git branch with color coding (red for main, green for develop)
- Model name as reported by Claude Code
- Thinking effort - three vertical bars, blue for active level, grey for inactive
- Context bar - 10-character progress bar, color shifts at 50% and 80%
- 5-hour usage - real percentage from the API with time until reset
- 7-day usage - same, with reset countdown
- Session stats - cost, duration, lines added/removed (dimmed)
- Platform health - green dot when operational, red when degraded
The Python script caches API responses for 60 seconds and refreshes in the background so it never blocks the statusline rendering. The bash script caches the Python output separately with its own TTL. Two layers of caching means the statusline always returns instantly.
statusline-usage.py (click to expand)
#!/usr/bin/env python3
"""Fetch Claude Code plan usage from Anthropic's OAuth API."""
import getpass, json, subprocess, sys, time
from datetime import datetime, timezone
from pathlib import Path
from urllib.error import URLError
from urllib.request import Request, urlopen
CACHE_PATH = Path("/tmp/claude-usage-cache.json")
CACHE_TTL = 60
USAGE_API_URL = "https://api.anthropic.com/api/oauth/usage"
STATUS_API_URL = "https://status.claude.com/api/v2/status.json"
def get_oauth_token():
try:
result = subprocess.run(
["security", "find-generic-password",
"-s", "Claude Code-credentials",
"-a", getpass.getuser(), "-w"],
capture_output=True, text=True, timeout=5)
if result.returncode != 0: return None
creds = json.loads(result.stdout.strip())
return creds["claudeAiOauth"]["accessToken"] or None
except: return None
def fetch_usage():
# Check cache first
try:
if time.time() - CACHE_PATH.stat().st_mtime < CACHE_TTL:
body = CACHE_PATH.read_text()
else: raise FileNotFoundError
except:
token = get_oauth_token()
if not token: return {"error": "no_token"}
req = Request(USAGE_API_URL, method="GET")
req.add_header("Authorization", f"Bearer {token}")
req.add_header("anthropic-beta", "oauth-2025-04-20")
try:
with urlopen(req, timeout=5) as resp:
body = resp.read().decode()
CACHE_PATH.write_text(body)
except: return {"error": "api_fail"}
data = json.loads(body)
result = {}
for key, total_min in [("five_hour", 300), ("seven_day", 10080)]:
window = data.get(key)
if isinstance(window, dict) and window.get("utilization") is not None:
resets = datetime.fromisoformat(
window["resets_at"].replace("Z", "+00:00"))
remaining = max(
int((resets - datetime.now(timezone.utc)).total_seconds()) // 60, 0)
result[key] = {"pct": round(window["utilization"]),
"remaining_min": remaining}
return result
# Also check platform status
req = Request(STATUS_API_URL, method="GET")
try:
with urlopen(req, timeout=3) as resp:
indicator = json.loads(resp.read()).get("status",{}).get("indicator","none")
status_map = {"minor":"degraded","major":"major_outage","critical":"critical_outage"}
status = status_map.get(indicator, "")
except: status = ""
output = fetch_usage()
if status: output["platform_status"] = status
json.dump(output, sys.stdout)
Design Choices
Catppuccin Mocha everywhere. I use Catppuccin across my terminal, editor, and prompt (Starship). Using the same palette in the statusline means everything feels cohesive. The specific colors - green (#a6e3a1), yellow (#f9e2af), red (#f38ba8) - are instantly recognizable as severity levels without needing to read numbers.
Symbols over text. The thinking effort started as [think:default] - functional but noisy. The three-bar indicator communicates the same information in three characters and matches what you see in Claude Code’s own /model picker. Platform status is a colored dot, not a word. Less visual clutter means the important numbers stand out.
Branch first. Git branch is the most common context switch indicator. Putting it first means you always know which codebase you’re affecting without scanning.
Background caching. The Python API call takes about 200ms. The JSONL transcript scanning (when I was using that approach) took 250ms. Either would add noticeable lag if run synchronously on every statusline update. Background refresh with atomic file writes (tmp + mv) means you always get a fast read from cache, with stale data being at most 60 seconds old. Good enough for a statusline.
Real data over estimates. This was the biggest lesson. The community has built multiple tools for estimating Claude Code usage from local data - ccusage for costs, various scripts for tokens. They’re all wrong because they’re working from incomplete information with guessed limits. The OAuth API returns the actual utilization percentage that Anthropic uses internally. If the data source exists, use it.
The Takeaway
Claude Code’s statusline is one of those features that looks minor but rewards investment. A well-configured statusline reduces cognitive load - instead of running /usage manually, the information is just there. And the customization surface is surprisingly deep once you start exploring what data is available.
The community tooling around Claude Code is growing fast. The most useful solution I found - the OAuth usage API - came from a GitHub gist with three stars, not from official documentation. If you’re building your own statusline, start with the reliable data (the JSON input), add the API usage data for plan tracking, and don’t bother with transcript-based estimates unless you enjoy calibrating inaccurate numbers.
The full source for both files is in the code blocks above. Drop them in ~/.claude/, configure statusLine in your settings.json, and you have accurate usage tracking in your statusline.