31 tools· updated sep 2026

statusLine: the Claude Code status line I rebuilt after a 13-second render

Updated Marek.

  • claude-code
  • agent-ops
  • context-engineering

statusLine is the setting in Claude Code that runs a script of your choosing after every assistant message and shows whatever it prints in a bar at the bottom of the terminal. The docs describe the input as JSON session data on stdin: model, context window, cost, rate limits, effort, open PR, worktree. What you print is your call. For a while my call was a tool that took 13 to 20 seconds per render.

  • What it is. One settings object, statusLine, with type set to command and a command to run. JSON in, stdout out. Per the docs it runs locally and spends no API tokens.
  • What the payload carries. context_window.used_percentage, current_usage with cache tokens split out, effort.level, rate_limits.five_hour and seven_day with resets_at, pr, worktree, agent.name, cost.
  • What mine shows. Model, agent name, effort, a 10-cell context bar with percent and used/size, 5-hour and 7-day limits, a PR badge, worktree or directory. 171 lines of Node, zero I/O, median 41 ms.
  • The detour. ccusage statusline scanned 916 files and 763 MB on every render, 13 to 20 seconds each. Its cache cut that to 0.27 s and, at a 60 s refresh interval, froze the context counter at 50,000 (5%).
  • The rule. This is the one command whose output sits on the screen after every message. Time it on your own data before you trust it, and again when your history grows.

Same Claude Code install whose skills I sorted by blast radius in the disable-model-invocation post; of all its settings keys, this one is on screen all day.

How do I set up a status line in Claude Code?

Two ways, both from Claude Code's docs. Type /statusline with a plain-English description and Claude Code writes a script into ~/.claude/ and updates your settings. Or add a statusLine object to ~/.claude/settings.json yourself, with type set to "command" and command pointing at a script or an inline shell command. Settings reload on save.

The manual route:

  1. Write a script that reads JSON from stdin and prints a line. The docs' smallest example uses jq to pull model.display_name, workspace.current_dir and context_window.used_percentage. Test it with the docs' one-liner that pipes a hand-written JSON object into the script.
  2. Make it executable with chmod +x on macOS and Linux. On Windows, read the Windows section below first.
  3. Add the object to ~/.claude/settings.json. Mine, path shortened:
{
  "statusLine": {
    "type": "command",
    "command": "node C:/path/to/statusline.mjs"
  }
}

Three optional fields sit next to command: padding for horizontal spacing, default 0; refreshInterval to re-run every N seconds on top of the event-driven updates, minimum 1, meant for clocks or for idle sessions where background subagents change git state; hideVimModeIndicator for scripts that render vim.mode themselves. /statusline delete removes the whole thing.

How does Claude Statusline work?

Claude Code runs your command with a JSON snapshot of the session on stdin and displays whatever the script prints to stdout. It runs once at session start, then again after every assistant message, after /compact, when the permission mode or vim mode changes, and on a refreshInterval timer if you set one. Updates are debounced at 300 ms.

The docs list eight triggers; the other three are a change to the command string itself, which skips the debounce, a rate-limit window reaching its resets_at, and a warm prompt cache reaching its expires_at. A new trigger cancels a script still running. And from the troubleshooting section, the sentence that explains my whole detour: slow scripts block the status line from updating until they complete.

The fields I care about, docs wording condensed:

FieldWhat it holds, per the docs
context_window.total_input_tokensTokens in the context window from the most recent API response, cache reads and writes included
context_window.used_percentagePre-calculated; may be null early in the session
context_window.current_usageinput_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens; null before the first call and right after /compact
effort.levellow, medium, high, xhigh or max; absent when the model has no effort parameter
rate_limits.five_hour, rate_limits.seven_dayused_percentage from 0 to 100 and resets_at in epoch seconds; Claude.ai Pro and Max subscribers, or behind a Claude apps gateway, and only after the first API response

Per the docs, total_input_tokens is the sum of input_tokens, cache_creation_input_tokens and cache_read_input_tokens, and used_percentage uses those three only, never output_tokens. This is the context window counter most people put a status line up for, measured in tokens the way the API counts them, cache included.

What my status line shows

The script is 171 lines of Node. Its first comment is the design: zero I/O, read the stdin payload and nothing else. No fs, no child_process, no git commands. Five groups, left to right; a group with no data disappears.

  1. Identity. model.display_name, then agent.name in parentheses when the --agent flag or agent settings set one, then effort.level. My settings pin effort at xhigh, so that word sits on the bar all day.
  2. Context. A 10-cell bar, the percent, and used/size in k or M. The percent comes from used_percentage, with total_input_tokens over context_window_size as fallback. Green under 50 percent, yellow from 50, red from 80. Hidden while total_input_tokens is zero.
  3. Limits. rate_limits.five_hour and seven_day as percentages, green under 60, yellow from 60, red from 85. The countdown to resets_at shows only from 85 up, because a reset time is noise until the limit is a problem.
  4. PR. pr.number as PR #n, or MR !n when pr.kind is mr. Green on approved, red on changes_requested, dim otherwise.
  5. Place. worktree.name with a wt: prefix inside a worktree session; otherwise the last segment of workspace.current_dir.

If the payload fails to parse, the script prints an empty string: a blank bar beats an error in the bar. Not shown: cost, the cache split from current_usage, and git branch or dirty state. Branch and staged counts need git calls, which is I/O, and I would rather have a bar that never touches the disk.

The ccusage detour: 13 seconds per render

Until 25 August 2026 the command in that settings key was ccusage statusline, version 18.0.10. It shows the day's cost and the current 5-hour block. On my machine a render took 13 to 20 seconds, and the control run says why: on every render it reads every transcript under ~/.claude/projects, a directory that held 916 files and 763 MB.

Same payload, same command, pointed at a directory with a single transcript: 0.14 s. Pointed at the real directory: 13.2 s. The payload was never the problem; the history scan was.

ccusage has a cache, and the cache brought a render down to 0.27 s. With --refresh-interval 60, I fed it three payloads carrying 50k, 300k and 777k tokens, and all three came back as 50,000 (5%). That frozen counter says the cache holds the whole output line, not the expensive part: the one number I put a status line up for stood still for a minute at a time. At a 1 s interval the counter stayed fresh, but every second or third render fell into the full scan again.

So I replaced it with the script above: median render 41 ms, zero I/O. The price was the dollar figures: the day total and the 5-hour block need the history scan by definition, so they left the bar and live in /cost and a manual ccusage run.

The dead workaround: a grep through the binary

The second thing 25 August killed was older. On 16 April 2026 I had written statusline-wrapper.mjs around a Claude Code bug: total_input_tokens did not include cache tokens back then, so on a cached conversation the counter showed 0 percent. The wrapper read the transcript, added the cache tokens and passed the corrected payload on.

Before deleting it I checked whether the bug still existed. grep -a -o -b "total_input_tokens" claude.exe on the 337 MB binary returned the implementation: total_input_tokens: e.input_tokens + e.cache_creation_input_tokens + e.cache_read_input_tokens. The docs say the same today. The workaround had been dead code for some time.

One more measurement, because I had been blaming the wrapper: reading a 59 MB transcript cost it 0.13 s, two orders of magnitude under the 13 s I was seeing. Measure before you delete the wrong thing.

statusline on Windows: the backslash problem

The docs' problem first: on Windows, Claude Code runs the status line command through Git Bash when it is installed, and through PowerShell otherwise. Git Bash treats unquoted backslashes as escape characters, so a command like C:\Users\you\script.mjs reaches the runner with its separators stripped and fails with no visible error. Write the path with forward slashes; ~ also works. A PowerShell script runs through powershell -NoProfile -File C:/Users/you/.claude/statusline.ps1.

Mine: an agent wrote the script through its Bash tool, which halved every run of backslashes on the way to disk. Each \\ arrived as a single \. The regex that splits a path on either separator, /[\\/]/, was saved as /[\/]/. node --check passed, the script ran, and the status line showed the entire directory path instead of its last segment. Three rules came out of it: forward slashes only in agent-written scripts, String.fromCharCode(92) when a backslash is needed, and a grep of the critical line after saving, because a syntax checker cannot catch a regex that is valid and wrong. The script now contains no literal backslash at all.

Why is my statusline not showing?

The docs' troubleshooting list, in the order I would check it. The script must be executable and print to stdout, not stderr. A non-zero exit or empty output blanks the line. On Windows with Git Bash, backslashes in the command path are the usual cause.

The status line runs under the same workspace trust rule as hooks, so until you accept the trust dialog it stays blank, and claude --debug logs Status line command skipped: workspace trust not accepted. Two settings can also kill it: disableAllHooks outside managed settings, and allowManagedHooksOnly set by an organization.

If the line shows but the numbers look wrong: fields may be null before the first API response, current_usage goes null again right after /compact, and the context percentage can differ from /context because the two are calculated at different moments. /context shows the same metric on demand, and /doctor is the command I lined up for the CLAUDE.md audit; the status line is the live version of the first.

What is the best statusline in Claude Code?

The best status line is the one that reads only stdin. Everything you need for context, effort, rate limits, PR state and worktree is already in the payload, and a script that touches nothing else renders in milliseconds. Any tool that scans your history on each render gets slower as you use Claude Code more. Mine did.

One keyword tool estimates 1,100 searches a month for "claude code statusline" as a twelve-month average, 738 last month, keyword difficulty 8. Page one of Google on 30 August 2026: the docs page, ccstatusline, statusline.sh (a drag-and-drop builder), ccusage's beta status line integration and a few personal write-ups.

I have not run ccstatusline or statusline.sh, so no verdict on either beyond the test I would apply to any of them: pipe the docs' mock payload in, time it, then point it at your real history and time it again. Over a second on the second run is the wrong tool for this slot. If it needs a cache to be fast, check what the cache freezes. The zero-effort answer is /statusline itself: a script that reads stdin and nothing else.

Do Cursor, Codex, Copilot and opencode have a status line?

Three of the four do. Codex shows built-in items you pick and reorder with /statusline. Cursor's CLI has one on/off switch, for elapsed time in the bar. GitHub Copilot CLI has a configurable footer and a statusLine.command hook that takes session JSON on stdin. opencode has no status line key in its docs.

Vendor pages re-read 15 September 2026; the GitHub Copilot CLI references were re-read on 18 September 2026.

AgentStatus lineWhat the docs say
Claude CodeYes, your scriptYour command gets session JSON on stdin; stdout is the bar.
Codex CLIYes, built-in itemstui.status_line, array<string> or null: "Ordered list of TUI footer status-line item identifiers. null disables the status line."
Cursor CLIYes, one switchdisplay.showStatusLineRunningTime: "Show elapsed running time in the status line". No script hook.
opencodeNo key in the docstui.json covers scrolling, diff style, cursor, mouse and attention alerts. No status line entry.
GitHub Copilot CLIYes, built-in items or your script/statusline and /footer configure the bar; statusLine.command receives session JSON on stdin and prints status content to stdout.
VS Code CopilotNot checkedNot read for this post.

opencode statusline: what exists and what does not

The opencode config page and TUI page both say "Last updated: Sep 14, 2026"; neither contains status line, status bar or footer. TUI settings live in their own tui.json: scroll_speed, scroll_acceleration, diff_style, cursor.style and cursor.blinking, mouse, and an attention block for notifications and sound. The TUI page is seventeen slash commands and keybinds. So as of 14 September 2026 an opencode statusline search gets a theme and a keymap from the docs, not a configurable bar.

Codex status line: tui.status_line

/statusline landed in openai/codex on 5 February 2026: a picker whose order is saved to tui.status_line in ~/.codex/config.toml. The developer commands page lists the choices: model, model with reasoning, context stats, rate limits, git branch, token counters, session id, current directory or project root, Codex version. The config reference gives the type, no identifiers, no default; ["spinner", "project"] on that page is the tui.terminal_title default, a different key. Mine, checked 15 September 2026, holds six: model-with-reasoning, context-used, used-tokens, five-hour-limit, weekly-limit, current-dir. What Codex lacks is a command hook: issue #17827 asks for one and has been open since 14 April 2026.

And ccstatusline for Codex: its README calls it "A highly customizable status line formatter for Claude Code CLI", it installs into Claude Code's statusLine setting.

The verdict

statusLine is a small setting with an unusual property: whatever you put there runs after every assistant message, and its output stays on screen all session. That makes it one place where a tool's cost scales with how much you use the agent, and my numbers say the scaling is real: 916 files, 763 MB, 13 to 20 seconds per render, a cache that froze the number I was watching, a workaround dead since the payload started summing cache tokens. The replacement is 171 lines that read stdin and print a line in 41 ms. Measure the thing in the bar on your own history, then again in three months.

Sources read on 30 August 2026; the cross-agent section was re-read on 15 September 2026. GitHub Copilot CLI references re-read on 18 September 2026. Customize your status line in Claude Code's official docs, source of every field name and trigger above; the Cursor CLI configuration reference; the Codex config reference and CLI customization pages; the opencode config page; and the GitHub Copilot CLI settings page. The numbers about my own setup come from my session notes of 25 August 2026 and from the script itself.