Skip to content

Repository files navigation

Claude Code Statusline

A multi-line status line for Claude Code, plus a per-row renderer for the subagent panel. Pure bash, one hard dependency (jq), and a single self-contained installer you can hand to a teammate.

Statusline example

Every segment is dropped when its data is absent, so the same script degrades cleanly across versions, plans and subscription types.

Install

curl -fsSL https://raw.githubusercontent.com/ProcessPal/claude-statusline/main/install-claude-statusline.sh -o install-claude-statusline.sh
bash install-claude-statusline.sh

Nothing else is needed — both scripts are embedded in that one file, so it also works fine over Slack, email or a USB stick.

./install-claude-statusline.sh              # install or upgrade
./install-claude-statusline.sh --dry-run    # show changes, write nothing
./install-claude-statusline.sh --uninstall  # revert settings, keep scripts
./install-claude-statusline.sh --help       # all flags
Flag Effect
--config-dir DIR Install into DIR (default $CLAUDE_CONFIG_DIR, else ~/.claude)
--refresh-interval N Re-run every N seconds (default 10, 0 disables)
--padding N Horizontal padding (default: unset)
--no-subagent Skip the subagent row renderer
--force Overwrite scripts that have local edits

The installer backs up settings.json before touching it, refuses to run against invalid JSON, preserves your other settings keys, keeps local edits to the scripts unless --force, and finishes with a smoke test so a blank status line surfaces at install time rather than three sessions later.

Multiple workspaces

Claude Code resolves its config from CLAUDE_CONFIG_DIR, falling back to ~/.claude. Parallel workspaces each get their own config dir and their own settings.json — a status line installed in one is invisible to the others.

Two ways to cover them.

One canonical script, every workspace points at it. Preferred once you have more than a couple: install normally, then set each workspace's settings.json to reference $HOME/.claude explicitly.

// <workspace>/config/settings.json
{
  "statusLine":         { "type": "command", "command": "bash \"$HOME/.claude/statusline-command.sh\"", "refreshInterval": 10 },
  "subagentStatusLine": { "type": "command", "command": "bash \"$HOME/.claude/subagent-statusline.sh\"" },
  "hideVimModeIndicator": true
}

Apply to a whole tree of workspaces:

cd ~/.claude/.claudes
for d in */; do
  s="$d/config/settings.json"; [ -f "$s" ] || continue
  cp "$s" "$s.bak"
  jq '.statusLine={type:"command",command:"bash \"$HOME/.claude/statusline-command.sh\"",refreshInterval:10}
    | .subagentStatusLine={type:"command",command:"bash \"$HOME/.claude/subagent-statusline.sh\""}
    | .hideVimModeIndicator=true' "$s" > "$s.tmp" && mv "$s.tmp" "$s"
done

One script to update, every workspace follows. If a workspace already holds its own copies of the scripts, replace them with symlinks rather than deleting — any ${CLAUDE_CONFIG_DIR}/... reference keeps resolving and cannot drift:

ln -sf "$HOME/.claude/statusline-command.sh"  <workspace>/config/statusline-command.sh
ln -sf "$HOME/.claude/subagent-statusline.sh" <workspace>/config/subagent-statusline.sh

Self-contained per workspace. Use when a workspace must survive on its own, at the cost of updating each one separately:

./install-claude-statusline.sh --config-dir ~/.claude/.claudes/my-workspace/config

--config-dir pins absolute paths in that workspace's settings; a bare install into ~/.claude uses ${CLAUDE_CONFIG_DIR:-$HOME/.claude} instead, so the same settings file resolves correctly from anywhere.

Caches namespace themselves per config dir automatically — a digest of the full path, not its name, since every workspace config dir tends to be called config. Concurrent workspaces never collide.

What it shows

  • Line 1 — model, effort level, context bar (green → red at 50/75/90/95%), cwd, git branch with unstaged/staged/ahead/behind counts, worktree name.
  • Line 2 — plan phase tracker (lowest-numbered ## Phase N heading that still has unchecked - [ ] items, across the newest plans/<dir>/*.md), version, vim mode, agent, session name, PR number with review state.
  • Lines 3–4 — 5h/7d rate-limit usage with local reset times, lines changed, session cost, duration.

Beyond the generic drop-when-absent rule, four segments have their own conditions: rate limits need a Claude.ai Pro/Max plan, the phase line a plans/ directory, max a model that takes an effort parameter, a linked worktree and an open PR on the branch.

Subagent rows

subagent-statusline.sh replaces the default name · description · token count row in the agent panel. It receives the whole panel as one JSON object — columns plus a tasks array — and writes one {"id","content"} line per row it wants to own.

🔄 scout [Explore] scanning repo for callers  ⏱ 1m 35s  🪙 15k 8%
✅ tester [general-purpose] ran suite  ⏱ 1h 2m  🪙 1.2M 62%
❌ a-very-long-agent-name-… [code-reviewer] review  ⏱ 30s
⏸  idle-one waiting

label wins over name and is trimmed to 24 cells; the description gets columns - 60. Status drives both emoji and colour across the running/idle/done/failed/blocked/cancelled vocabularies. Type, description, duration and tokens are each dropped when absent.

The percentage beside the token count is that row's own context usage — tokenCount against the task model's contextWindowSize — coloured on the same 50/75/90 thresholds as the main context bar. contextWindowSize arrived in Claude Code 2.1.205 and is absent until a task's model resolves, so the percentage drops out rather than reading 0%.

Two affordances from the spec go unused: omitting a task's id keeps Claude Code's default row for it, and an empty content hides a row outright. This script emits a row for every task.

Requirements

  • Claude Code 2.x — uses context_window and rate_limits stdin fields
  • bash, jq, plus the POSIX base (awk, sed, find, git)
  • macOS and Linux. On Windows use Git Bash and forward slashes in the settings path — backslashes get eaten as escape characters.

There is no bc or GNU-coreutils requirement: digests fall back through shasumsha1summd5sumcksum, and every stat/date call has a BSD form with a GNU fallback.

Design notes

Points where this follows the official statusline docs, since they're easy to get wrong:

  • Cache keys use session_id, never $$. The docs call this out specifically: a pid changes on every invocation and silently defeats the cache. Git state is cached 5s, the plan-phase scan 30s and refreshed in a detached background job.
  • Caches are namespaced per config dir. Sandbox configs are all named config, so the namespace is salted with a digest of the full path (config-3581e2cb) rather than the directory name. Legacy fixed-name cache dirs get swept on upgrade.
  • hideVimModeIndicator is set because this script draws the vim badge itself; without it Claude Code renders -- INSERT -- a second time.
  • refreshInterval defaults to 10s because the status line carries time-based data (duration, reset countdowns) and git state that background subagents mutate while the main session sits idle. Event-driven updates alone would leave those stale.
  • printf '%b' instead of echo -e, which the docs recommend for reliable escape handling.
  • Fails loud, not blank. A non-zero exit or empty stdout blanks the status line, so a missing jq prints a message instead of vanishing.

Output is deliberately kept fast: the script runs on a 300ms debounce and an in-flight run is cancelled if a new update arrives.

Latent segments

Two segments are computed but never rendered — they survive from an earlier layout and are each one line away from working in Assemble output:

  • token_statsin:85K out:12K/200K. Note the source fields changed meaning in Claude Code 2.1.132: context_window.total_*_tokens are what's currently in the window, not session totals.
  • ctx_emoji → the 🟢/🟡/🟠/🔴/🚨 threshold emoji beside the bar

The stdin schema carries a good deal more that this script ignores: model.id, workspace.added_dirs, workspace.repo.{host,owner,name}, cost.total_api_duration_ms, context_window.current_usage.* (cache reads and writes broken out), exceeds_200k_tokens, thinking.enabled, prompt_id, transcript_path, output_style.name and pr.url.

worktree.* is unused and is not what the badge reads. It exists only for --worktree sessions, whereas workspace.git_worktree — the badge's source — fires for any linked worktree. They are easy to confuse.

On the subagent side, per-task model is unread: it names the model behind contextWindowSize, and the size alone is what the row percentage needs.

Troubleshooting

  • Nothing appears — the status line only runs after you accept the workspace trust prompt. disableAllHooks: true also disables it. Run claude --debug to see the exit code and stderr from the first invocation.
  • Values are empty — fields are null until the first API response lands.
  • No rate-limit lines — those only exist for Claude.ai Pro/Max accounts.
  • Garbled output — multi-line output with heavy ANSI is the most glitch-prone combination; drop --refresh-interval to 0 or simplify if your terminal struggles.

Test any change with mock input before trusting it:

echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/tmp"},
"context_window":{"used_percentage":25,"remaining_percentage":75},
"session_id":"test"}' | ./statusline-command.sh

The bar needs both used_percentage and remaining_percentage; pass only the first and it renders nothing, which looks like a fault but isn't.

Developing

statusline-command.sh and subagent-statusline.sh are the sources of truth. After editing either, regenerate the distributable so it can't drift:

./build-installer.sh    # tests, syntax-checks, embeds, verifies, stamps the build

The build runs tests/run.sh first and refuses to embed a failing script. Run the suite directly while iterating:

./tests/run.sh                          # everything
./tests/git-indicators.sh               # one suite

Both suites render the real scripts and assert on visible text with ANSI stripped. git-indicators.sh builds a throwaway repo with an upstream so ahead/behind actually resolve; each case uses a fresh session_id because the git cache has a 5s TTL keyed on it. Cases that aren't about elapsed time omit startTime — pinning a second-scale duration would race the suite's runtime.

About

Multi-line Claude Code status line + subagent row renderer, with a single self-contained installer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages