Building a custom Claude Code status line
On this page6 sections ▾
I run Claude Code across multiple projects, often with several Ghostty tabs open at once. Every session looks the same from the bottom of the terminal: a model name, a context percentage, and a cost. When you're juggling three repos, that's not enough.
Which repo am I in? What GitButler branch is active? Am I close to my 5-hour rate limit? How much have I spent today across all sessions (not that it matters on a Max plan, but it gives you a feel for what this type of workload would cost as an API deployment)? The default status line doesn't tell me.
#Why I built my own
Funny timing, Daniel Mackay published a blog post about building a custom Claude Code status line at the same time I was busy writing mine. His LinkedIn post described the motivation this way:
The cost of building has dropped below the cost of compromising.
I considered forking Dan's repo and submitting a PR, but my requirements were quite specific. I wanted GitButler virtual branch detection, multi-line layout with grouped sections, goccc integration for cost tracking in AUD, rate limit progress bars, and terminal tab title renaming. Those are my opinions about what a status line should show, and they're probably not the same as his.
I had an initial version after about 15 to 30 minutes with Claude Code. The full session, including iterations, GitButler research and packaging, took about two hours and produced around 250 lines of bash.
#How the statusLine hook works
Claude Code has a built-in mechanism: add a statusLine config to your settings.json, point it at a shell script, and Claude pipes a JSON blob to stdin on every tick. Whatever your script prints to stdout becomes the status line.
{
"statusLine": {
"type": "command",
"command": "~/.claude/scripts/statusline.sh"
}
}These are the JSON fields used by the script:
{
"model": { "id": "claude-opus-4-6", "display_name": "Opus 4.6" },
"cost": { "total_cost_usd": 0.85, "total_duration_ms": 3600000 },
"context_window": {
"used_percentage": 11,
"context_window_size": 200000,
"total_input_tokens": 45000,
"total_output_tokens": 12000
},
"workspace": { "current_dir": "/path/to/repo" },
"rate_limits": {
"five_hour": { "used_percentage": 23.5, "resets_at": 1774200000 }
}
}#Building it
I went through four iterations in a single Claude Code session.
#v1: repo and branch info
The first version wrapped goccc (a CLI cost calculator that handles session costs, daily totals, and currency conversion) and prepended the repo name and branch. One line, straightforward.
The tricky part was GitButler detection. I use GitButler for branch management, which keeps you on a gitbutler/workspace branch while your actual virtual branches live inside GitButler's system. A normal git branch --show-current would just say "gitbutler/workspace", which isn't helpful.
So I check if we're on gitbutler/workspace, and if so, query GitButler's CLI for the actual active branches:
current_branch=$(git branch --show-current 2>/dev/null || echo "")
if [[ "$current_branch" == "gitbutler/workspace" ]]; then
branches=$(but branch list --no-check --no-ahead --json 2>/dev/null \
| jq -r '.appliedStacks[].heads[].name' 2>/dev/null \
| paste -sd ',' - 2>/dev/null \
| sed 's/,/, /g' || true)
branch_info="🌿 ${branches}"
elif [[ -n "$current_branch" ]]; then
branch_info="🔀 ${current_branch}"
fiThe --no-check --no-ahead flags keep it fast (~30ms). The 🌿 icon means GitButler, 🔀 means regular git. You can tell at a glance which mode you're in.
#v2: rate limits and tokens
Turns out Claude Code exposes rate limit data in the JSON: rate_limits.five_hour.used_percentage and resets_at. Much better than trying to calculate from duration. I added a color-coded progress bar (green → yellow → red) with time remaining until reset.
I also added token counts, input and output totals, since the context percentage alone doesn't tell you how chatty the session has been.
#v3: multi-line grouped layout
Everything on one line was getting cramped. I split it into three lines, each grouped by type of information:
📂 xylem · 🌿 gb-branch-5 · 🤖 Opus 4.6
💸 A$1.21 session · 💰 A$48.00 today · ⏱️ ██░░░░░░░░ 23% 4h0m left
💭 █░░░░░░░░░ 11% ctx · 🧠 45k in / 12k outLine 1 (Identity): where am I? Repo name, active branch, model. Line 2 (Spend & limits): session cost, daily cost, rate limit bar with countdown. Line 3 (Technical): context window usage, token counts.
The grouping keeps project identity separate from costs and usage, so I can find the relevant figure without scanning the whole display.
#v4: hiding empty state
A new session starts with everything zeroed out. Showing ⏱️ 0h0m and 💭 0% ctx before you've even sent a message just adds noise. So the script hides those sections until there's actual data. A fresh session shows just two lines:
📂 claude-statusline · 🌿 gb/update-readme · 🤖 Opus 4.6 (1M context)
💰 A$41.06 todayOnce the session is active, the full three-line display appears.
#Packaging it
I wanted this installable on any machine, so I created a repo with:
statusline.sh, the script itselfinstall.sh, which installs goccc via Homebrew, copies the script to~/.claude/scripts/, and prints the settings.json config- Built-in daily auto-update: the script checks once per day if there's a newer version on
mainand pulls it in the background
One-liner install:
curl -sSL https://raw.githubusercontent.com/gordonbeeming/claude-statusline/main/install.sh | bash#Other tools I'm building
I've been taking a similar approach with SSMSX, a cross-platform SQL Server Management Studio replacement. SSMS is Windows-only, and I wanted something faster than the tools I was using. That project uses Tauri v2 (Rust), React, and a C# Native AOT sidecar for SQL Server connectivity, and I've been working on it in evenings and weekends.
The status line was a much smaller task: a script that shows the repo, branch, costs and limits I need while working.
#Try it
The repo is at github.com/GordonBeeming/claude-statusline. You can install it, or read the Claude Code statusline docs and build your own. The JSON schema has more fields than I'm using, so there's plenty to work with.