Skip to content

About

Two-line ANSI statusline for Claude Code: git + k8s context, rate-limit bars, service health, AI session topics

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Latest commit

 

History

102 Commits

Folders and files

Repository files navigation

cc-statusline

ci release license

A two-line, ANSI-colored statusline for Claude Code with project-aware colors, git status, Kubernetes context, rate-limit bars, Claude service health, and the session's name and title.

cc-statusline screenshot

Design notes, screenshots, and the story behind the script: Custom Claude Code Status Line on hai.wxs.ro

Features

  • Selectable themes with adaptive two-line layouts; Tokyo Auto follows OS appearance, and Classic preserves the original look
  • Per-project colors in Classic and Hue Dark (12-color palette, hashed from session/cwd, manually overridable)
  • Git info: branch, staged/modified/untracked counts
  • Kubernetes context: current kubectl context (with timeout to avoid exec-auth hangs)
  • Session metrics: model name, effort level (low/medium/high/xhigh/max, the live session value Claude Code reports), elapsed time, Claude session cost in USD, or a GPT-5.6/6.1 Sol ChatGPT credit-equivalent estimate (211.29 cr, compacted to 2.07k cr at four digits), plus the official Codex available balance (54.91k cr) when provided
  • Context window: colored bar and percentage; on GPT sessions a transient all-zero usage report re-shows the last valid percentage in dim gray (see GPT/Codex plan usage)
  • Cache hit rate: prompt-cache efficiency of the last API call (green when most of the context is cached, coral when cold); off by default, enable with STATUSLINE_CACHE=1 (hidden anyway before the first call and after /compact)
  • Prompt-cache timer: how long until the main conversation's prompt cache goes cold, after the hit rate on line 2: a fire icon and the minutes left (42m, <1m in the last minute; green, then gold, then a coral fire-alert icon in the last 20% of the TTL, with a dim ·5m tag when the cache TTL is 5 minutes), then a snowflake and the tokens your next message re-caches (184k) once cold. Read from Claude Code's .prompt_cache (v2.1.251+); hidden on older versions, when caching is off, and on OpenAI-backed (GPT) panes, whose cache lifetime Claude Code cannot report. On by default, hide with STATUSLINE_CACHE_TIMER=0
  • Context fill on phone: the phone/slim layout shows ctx NN% (context-window usage, same color thresholds as the rate limits) before the 5h/7d windows; on by default, hide with STATUSLINE_CTX=0. The wide layout already shows context as NN% of NNNk.
  • Rate limits: 5h and 7d bars with reset countdowns, progressively compacted to fit available width; Claude limits are shared across sessions and omit 5h when absent from the account response; opt-in GPT limits come from the official Codex CLI and render whichever windows are available
  • Pace arrows: optional ↑/→ after a rate-limit % projecting whether you'll exhaust the window before it resets (coral = will overshoot, gold = on pace, nothing = safe); GPT projections use each Codex window's reported duration
  • Provider service status: line 2 follows the active model, using Claude's status by default or the exact OpenAI Codex API component for opt-in GPT sessions; auto-refreshed every 60s into separate caches
  • GitHub service status: line-1 icon (same glyphs/colors as the provider one), shown on repos with a github.com remote; on by default, disable with STATUSLINE_GITHUB_STATUS=0
  • Clickable status icons: both service-status icons are OSC 8 hyperlinks (GitHub icon to githubstatus.com, provider icon to status.claude.com or status.openai.com), so Cmd+click (macOS) / Ctrl+click opens the status page in a supporting terminal; on by default, disable with STATUSLINE_HYPERLINKS=0
  • Update indicator: a gold ⇡ X.Y.Z at the right edge of line 1 when a newer cc-statusline release exists (checked hourly against GitHub, hyperlinked to the release page); hidden entirely when you are current, disable with STATUSLINE_UPDATE_CHECK=0
  • Sessions in this repo: after the @handle on line 1 when another Claude Code session works in the same repository, a repo-wide count of every session by state (⚙ busy, ◷ background shells running, ? waiting on your answer, ○ idle), with the count your own session belongs to bracketed; disable with STATUSLINE_PEERS=0
  • Session name (@handle): the addressable name other Claude sessions use to message this one (Claude Code's per-session registry), shown first on line 1; on by default, hide with STATUSLINE_SESSION_NAME=0
  • Session title: Claude Code's auto-generated description, remembered across a user rename and hidden when it duplicates the visible handle; hide with STATUSLINE_TOPIC=0
  • Tab title: sets the terminal tab title from the session title or directory; disable with STATUSLINE_TAB_TITLE=0
  • Width-aware truncation: K8s context, branch, title, and name shrink first to keep line 1 under the soft limit before Claude Code's cli-truncate drops line 2

Requirements

  • macOS or Linux
  • bash 3.2 or newer
  • jq
  • perl (for ANSI-aware width measurement)
  • curl (for service status)
  • GNU timeout (coreutils; not stock on macOS)
  • Optional: fzf for the interactive theme picker with live previews
  • Optional: a recent official codex CLI logged in with ChatGPT for STATUSLINE_GPT_LIMITS=1
  • A Nerd Font in your terminal for the icons

Per-OS dependency install

OS Command
macOS (Homebrew) brew install bash jq perl curl coreutils
Debian/Ubuntu sudo apt install bash jq perl curl
Fedora/RHEL sudo dnf install bash jq perl curl
Arch sudo pacman -S bash jq perl curl
Alpine apk add bash jq perl curl coreutils

The Homebrew install below pulls these in automatically, except bash (your system's copy works; the scripts avoid bash-4-only features) and the font.

Install

Homebrew (recommended)

brew tap vtmocanu/tap
brew trust vtmocanu/tap    # Homebrew 6.0+ requires trusting third-party taps
brew install cc-statusline

On Homebrew older than 6.0 the brew trust line doesn't exist; skip it (brew install vtmocanu/tap/cc-statusline also works there as a one-liner). If you'd rather not trust the whole tap, brew trust --formula vtmocanu/tap/cc-statusline scopes it to this formula.

The install puts cc-statusline on your PATH, declares the dependencies, and prints the settings.json snippets to paste (also shown by brew info cc-statusline). Point Claude Code at it:

{
  "statusLine": {
    "type": "command",
    "command": "cc-statusline",
    "refreshInterval": 60
  }
}

Choose a theme with cc-statusline-theme (see Themes). The chooser is included in the install.

Upgrades are just brew upgrade cc-statusline; no settings change across versions.

Hacking on a clone while keeping the brew setup? The wrapper reads a working-tree path from ${XDG_CONFIG_HOME:-$HOME/.config}/cc-statusline/dev-dir; delete that file to return to the installed copy:

D="${XDG_CONFIG_HOME:-$HOME/.config}/cc-statusline"
mkdir -p "$D"
printf '%s\n' ~/src/cc-statusline > "$D/dev-dir"

install.sh (any platform, no Homebrew)

git clone https://github.com/vtmocanu/cc-statusline.git
cd cc-statusline
./install.sh

The installer extracts the chosen ref via git archive (so it never mutates your working tree), copies the scripts into ~/.local/share/cc-statusline/, and prints the JSON snippets you need to paste into ~/.claude/settings.json.

Choose a theme with ~/.local/share/cc-statusline/cc-statusline-theme, or the same filename under your custom install prefix (see Themes).

To pin a specific release:

./install.sh --version v2.1.0

To uninstall:

./install.sh --uninstall

To install into a custom prefix:

CC_STATUSLINE_PREFIX=/opt/cc-statusline ./install.sh

Manual install

If you'd rather skip install.sh, point statusLine.command directly at your clone:

{
  "statusLine": {
    "type": "command",
    "command": "bash /absolute/path/to/cc-statusline/statusline.sh",
    "refreshInterval": 60
  }
}

refreshInterval (seconds) re-runs the statusline on a timer in addition to activity-driven updates, so idle sessions keep fresh rate-limit reset times, service health, and usage bars. It requires a recent Claude Code version; remove the line to update only on activity. Rate-limit bars specifically stay fresh across all your sessions, not just the one being refreshed: each render shares the freshest known account-wide values with the others via a small per-user cache (STATUSLINE_RL_SHARE=0 to disable). The cache is keyed per account: sessions launched with CLAUDE_CODE_OAUTH_TOKEN=... claude get their own cache file (keyed by a hash of the token, never the token itself), so different accounts never see each other's bars.

The clone also includes the chooser: bash /absolute/path/to/cc-statusline/cc-statusline-theme.

No hook is needed for the session name or title: line 1 reads both from Claude Code directly (the @handle from its per-session registry, the descriptive title from the .session_name payload field).

Configuration

Color overrides

classic and hue-dark give each project a hashed color from a 12-color palette. To pin a project to a specific color, create ~/.claude/statusline-color-overrides.json:

{
  "/Users/me/code/important-project": 3,
  "/Users/me/code/other-project": 7
}

The key is the project root (resolved via git rev-parse --show-toplevel); the value is a palette index 0-11. See examples/statusline-color-overrides.json for the full palette mapping.

Environment variables

Variable Default Purpose
STATUSLINE_THEME tokyo-auto Select a built-in theme (see Themes below). Overrides the saved theme file; unknown or empty values use tokyo-auto. default is an alias for tokyo-auto.
CC_STATUSLINE_APPEARANCE auto Force dark or light for Tokyo Auto, useful for previews and tests.
CC_STATUSLINE_APPEARANCE_CACHE private state dir Override the appearance cache path for tests or integrations.
STATUSLINE_WIDTH 110 Maximum visible columns per line, and a hard cap: when Claude Code reports a narrower viewport (see below), the render follows the viewport instead. Lower this if you see line 2 disappearing.
STATUSLINE_LAYOUT auto phone or wide forces a layout; auto picks from the reported viewport width.
STATUSLINE_PHONE_COLS 60 Viewport width below which auto always selects the phone layout. Above it, auto still falls back to phone when the wide line 2 measurably does not fit (see below).
STATUSLINE_CACHE 0 Set to 1 to show the prompt-cache hit-rate readout (⚡ NN%) on line 2 (off by default).
STATUSLINE_CACHE_TIMER 1 Set to 0 to hide the prompt-cache cooldown timer on line 2 (fire + minutes left while warm, snowflake + tokens to re-cache once cold), independent of STATUSLINE_CACHE. When line 2 is short on room it keeps the timer and drops the hit rate first. Claude Code redraws when the cache expires, but the minutes only count down while idle with refreshInterval set. Hidden on GPT panes.
STATUSLINE_CTX 1 Set to 0 to hide the context-fill segment (ctx NN%) on the phone/slim layout's line 2. Shown before the rate limits with the same color thresholds; the first line-2 segment to shed as the viewport tightens. The wide layout's context readout is unaffected.
STATUSLINE_SESSION_NAME 1 Set to 0 to hide the @handle (the addressable session name peers message, read from Claude Code's per-session registry) at the start of line 1.
STATUSLINE_PEERS 1 Set to 0 to hide the repo-wide session counts after the @handle on line 1 (see Sessions in this repo).
STATUSLINE_TAB_TITLE 1 Set to 0 to skip the terminal tab-title write to /dev/tty.
STATUSLINE_TOPIC 1 Set to 0 to hide the auto description on line 1, remembered across a user rename.
STATUSLINE_GITHUB_STATUS 1 Set to 0 to hide the GitHub service-status icon on line 1 after the branch (same glyphs/colors as the Claude icon). Shown only when the current repo has a github.com remote; polls githubstatus.com every 60s in the background.
STATUSLINE_HYPERLINKS 1 Set to 0 to disable the OSC 8 hyperlinks on the service-status icons (GitHub plus the active Claude/OpenAI provider). Needs a terminal that supports OSC 8 (Ghostty, iTerm2, Kitty, WezTerm); elsewhere the escape is swallowed and the icon shows as plain text.
STATUSLINE_UPDATE_CHECK 1 Set to 0 to disable the update indicator (the gold ⇡ X.Y.Z right-aligned on line 1 when a newer cc-statusline release exists). Polls the GitHub "latest release" endpoint once an hour in the background; shown only when the release is newer than the installed VERSION, never when current.
STATUSLINE_PACE 1 Set to 0 to hide the rate-limit pace arrows (↑/→) on line 2.
STATUSLINE_COST 1 Set to 0 to hide the Claude session-cost readout (· $N.NN, sub-cent shown as $<0.01). It does not control GPT credits.
STATUSLINE_GPT_CREDITS 1 with GPT limits Set to 0 to hide the transcript-derived GPT-5.6/6.1 Sol ChatGPT credit-equivalent estimate. The public feature remains off until STATUSLINE_GPT_LIMITS=1.
STATUSLINE_GPT_CREDITS_TTL 300 Maximum age in seconds for a displayed GPT credit estimate; refresh attempts are throttled to 60 seconds per session.
STATUSLINE_RL_SHARE 1 Set to 0 to disable the shared per-user rate-limits cache (no read, no write).
STATUSLINE_RL_FETCH 1 Set to 0 to disable the background per-account usage fetcher (claude-usage-fetch.sh), which asks api.anthropic.com/api/oauth/usage with the session's own credential so multi-account machines show each account's true bars instead of Claude Code's shared (account-agnostic) numbers.
STATUSLINE_RL_AUTH_TTL 300 Seconds a fetched usage snapshot stays authoritative (displayed over the stdin rate_limits).
STATUSLINE_RL_BACKOFF 300 Seconds to stop fetching for an account after the usage endpoint returns an HTTP error (e.g. 429) with no usable fallback.
STATUSLINE_RL_PROBE 1 Set to 0 to disable the Messages-API header probe used when the usage endpoint refuses the credential (the probe costs a token or two of quota).
STATUSLINE_GPT_LIMITS 0 Set to 1 to replace Claude rate limits with the logged-in Codex CLI account's ChatGPT plan limits and available credit balance when the effective model ID identifies a GPT OAuth route.
STATUSLINE_GPT_FETCH 1 Set to 0 to keep reading a fresh GPT cache without launching codex-usage-fetch.sh. Has no effect unless STATUSLINE_GPT_LIMITS=1.
STATUSLINE_GPT_AUTH_TTL 300 Maximum age in seconds for displayed Codex windows and available credit balance. A stale snapshot is hidden rather than replaced with Claude limits.
STATUSLINE_GPT_BACKOFF 300 Seconds to pause Codex usage reads after an app-server, login, or payload failure.
CC_STATUSLINE_RL_KEY auto Override the rate-limits cache account key (a label like work). Normally auto-detected from the session's CLAUDE_CODE_OAUTH_TOKEN (hashed, read from the parent claude process's exec-time environment since Claude Code consumes the variable). Set empty to force the shared unsuffixed cache.
STATUSLINE_GLYPH_MARGIN 3 Columns reserved for Nerd Font glyphs that render double-width in some terminals. Set to 0 on a known mono-width font to reclaim them.
STATUSLINE_PROFILE 1 Set to 0 to hide the account/profile badge (see below).
STATUSLINE_CTX_EXPORT 0 Set to 1 to write a per-session context record for other local tools (see Context export).
CC_STATUSLINE_CTX_EXPORT_DIR ${XDG_STATE_HOME:-~/.local/state}/cc-statusline/ctx Override the context export directory.
STATUSLINE_DEBUG unset Set to 1 to write stderr to /tmp/statusline-debug.log.
CC_STATUSLINE_PREFIX ~/.local/share/cc-statusline Install prefix for install.sh.

Themes

Run cc-statusline-theme to choose interactively. With optional fzf, the choices sit above a full-width live preview of the two statusline rows. The current theme is marked and selected initially when fzf supports it. Without fzf, the chooser shows previews and a numbered menu.

cc-statusline-theme                  # choose interactively
cc-statusline-theme list             # list themes and mark the current choice
cc-statusline-theme current          # show the theme, resolved variant and source
cc-statusline-theme set classic      # restore the original look
cc-statusline-theme reset            # remove the saved choice
cc-statusline-theme preview          # preview every theme
cc-statusline-theme preview tokyo-day # preview one theme

Homebrew puts the chooser on PATH. With install.sh, use ~/.local/share/cc-statusline/cc-statusline-theme (or your custom prefix). For a manual clone, use bash /absolute/path/to/cc-statusline/cc-statusline-theme. Previews use the actual renderer with isolated sample data and caches, without network fetches or terminal tab-title changes. The chooser honors the same dev override as the statusline.

Choices are saved atomically to ${XDG_CONFIG_HOME:-$HOME/.config}/cc-statusline/theme and apply on the next redraw: immediately in active sessions, or at your configured statusLine.refreshInterval in idle sessions. Without a refresh interval, idle sessions update on your next message.

An explicit STATUSLINE_THEME assignment in statusLine.command wins over the saved file. The chooser recognizes leading literal assignments, optionally following env, without evaluating or expanding the command. Its own shell STATUSLINE_THEME is next, then the saved file, then tokyo-auto. The chooser shows that source and warns when settings prevent a saved choice from applying. Unknown or empty choices use tokyo-auto; default is an alias for it. With no explicit choice, an existing color-overrides file retains classic.

tokyo-auto follows the host OS appearance, selecting tokyo-night or tokyo-day; it does not inspect the terminal background. Appearance is cached for 60 seconds and refreshed on a subsequent redraw. Missing tools and failed probes fall back to Night. current and the picker show the resolved variant.

You can also select a theme directly in your statusline command:

STATUSLINE_THEME=tokyo-night cc-statusline
Theme Appearance
classic The original look: per-project hue, slanted caps, black second line.
hue-dark Project hue on a dark tint, with vertical edge caps.
nord Transparent backgrounds and a restrained Nordic palette.
phosphor CRT green and ASCII bars, with distinct amber/red alerts.
synthwave Pink, purple, and cyan gradient, with a dusk second line.
tokyo-auto Follows OS dark/light appearance, with a 60-second cache.
tokyo-day Tokyo Night's official Day colors with arrow segments.
tokyo-night Neon on navy, with stepped Powerline arrows.
gruvbox Warm earth tones, hard arrows, and block bars.
dracula Purple and pink segments, with flame joins.
catppuccin Mocha pastel capsules and round bars.
default Alias for tokyo-auto.

Classic retains the existing output byte for byte. Project color overrides and project hues apply to classic and hue-dark; the other themes use fixed palettes. All themes keep the same content, status meanings, hyperlinks, and adaptive width rules. A theme's caps and separators can change how much detail fits at a given width. Powerline caps require a Nerd Font, like the existing icons.

Phone layout (narrow viewports)

Claude Code exports COLUMNS/LINES to the statusline process (v2.1.153+), and it reports the viewport of the client doing the viewing: the same session rendered from the Claude mobile app arrives with COLUMNS=52 while the desk terminal renders it at COLUMNS=324. Each attached client gets its own render, so both can be right at once. Below STATUSLINE_PHONE_COLS (60) the script switches to a layout built for that width, and above it the switch still happens whenever the wide render measurably will not fit: line 2's wide base (model, effort, clock, cost, context) cannot be truncated, so the tier is re-decided after measuring it. That threshold therefore moves with your own line: a long model display name or a 5-figure cost can select the phone layout as high as the low 90s, and a lean session stays wide down to 60. STATUSLINE_LAYOUT=wide overrides this if you would rather have the wide render and accept the truncation.

The phone layout:

 󰉋 phone │  devmetaminds/phone !1 ?1
 wxs │ ctx 46% │ 5h 77%↑ ↻2h28m │ 7d 81%↑ ↻4d8h │ ✓

Line 1 keeps the folder, branch and dirty markers; line 2 keeps the account badge, the context fill (ctx NN%), and available rate-limit windows with their pace arrows and reset countdowns (↻). The session name and title, model, effort, elapsed, cost, credit estimate, available credit balance, and cache are dropped: at 50 columns they cost more room than they earn. As the viewport narrows further, each line sheds independently. Line 2 drops the reset countdowns first (keeping ctx and the bare percentages), then drops ctx so the rate limits themselves always survive (STATUSLINE_CTX=0 removes ctx entirely). Line 1 collapses the folder to its leaf, trims the branch (keeping its tail, since worktree branches share long prefixes), drops the dirty markers, trims the folder, and at the very narrowest drops the branch entirely so the leaf folder survives: at that width, which session this is matters more than which branch it is on.

No configuration is needed. STATUSLINE_LAYOUT=phone|wide forces a layout, and a one-line $XDG_CONFIG_HOME/cc-statusline/layout file holding phone or wide does the same for a running session (useful for clients that report no viewport); the environment variable wins over the file, and the file wins over auto-detection.

Note that the tier follows the effective width, which is STATUSLINE_WIDTH capped by the reported viewport. So an explicit STATUSLINE_WIDTH below STATUSLINE_PHONE_COLS selects the phone layout even on a wide terminal. That is deliberate (the layout should match the width the line is being fitted to), but if you set a narrow STATUSLINE_WIDTH as a truncation workaround and want the full render anyway, pin it with STATUSLINE_LAYOUT=wide.

Account/profile badge (multi-account setups)

Claude-only: Codex-detected sessions hide this badge, including when only the transcript identifies GPT, regardless of STATUSLINE_GPT_LIMITS. The badge identifies a Claude login, not the Codex account.

Opt-in: create ~/.claude/profile-labels.json with a profiles map and the badge shows which account a session is on (· MM), colored per profile. Entries are keyed by the account UUID from ~/.claude.json (.oauthAccount.accountUuid, keychain logins), or, for sessions launched with CLAUDE_CODE_OAUTH_TOKEN=... claude, by the cksum hash of that token (the same key the per-account rate-limits cache uses; an unlabeled account shows NNNNNN? so you know what to add):

{
  "enabled": true,
  "profiles": {
    "ed3f1226-c7fe-45ef-af44-6d7fb62175d0": { "label": "home", "color": "orange" },
    "4258859386":                            { "label": "work", "color": "red" }
  }
}

Session name and title

Line 1 leads with two identifiers Claude Code provides natively, so neither needs a hook or an API call:

  • @handle (the session name): the short, addressable name other Claude sessions use to message this one (SendMessage({to: "<handle>"})), for example @uzi-60. It is read from Claude Code's per-session registry under ~/.claude/sessions/*.json (the .name field, matched to the session by its id), which reflects both the auto-derived default and a /rename. Hide it with STATUSLINE_SESSION_NAME=0. This registry is an internal Claude Code file, so the read is best-effort: if its shape changes in a future release, the handle simply doesn't show.
  • Title (the descriptive label): the auto-generated .session_name, for example Add session names to status line. The statusline remembers each non-empty auto title in a private per-session cache. After a user rename, it shows that remembered description; if none was captured, it shows only the handle. A description matching the visible handle is hidden (case-insensitive), so @cc is not followed by cc. Hide the description with STATUSLINE_TOPIC=0; hiding the handle leaves the description available. Title retrieval never reads the transcript, and empty title frames preserve the cache. The tab title uses the displayed description or falls back to the directory.

Earlier versions synthesized the title with an opt-in UserPromptSubmit hook that called Claude Haiku; that hook has been removed in favor of the native field, which needs no credential, transcript excerpt, or quota. If you registered that hook in settings.json before upgrading, remove its UserPromptSubmit entry: otherwise Claude Code prints session-topic-capture.sh: No such file or directory on every prompt (harmless but noisy). The stale ~/.claude/session-topics/ cache it wrote is safe to delete.

Sessions in this repo

When more than one Claude Code session works in the same repository, line 1 shows a repo-wide count of all of them, your own included, after the @handle (or first when the handle is hidden), for example [⚙2] ◷1 ?1 ○2:

Glyph Meaning
⚙N busy: a turn is running (a running background subagent keeps a session busy too)
◷N the turn ended but background shells are still running, e.g. a watcher waiting on a delegated run
?N idle, and its last reply asked you something (an unanswered question prompt, or its final line ends with ?); drawn in reverse video so it stands out
○N idle and asked nothing: probably finished, a candidate for a final check

Zero counts are left out, and the whole segment is hidden when a session is alone in its repo. The count your own session belongs to is bracketed ([⚙N], [◷N] or [○N]), so each session shows where it stands; it is never counted as ?, since you are already looking at it. "Same repo" follows git worktree list, so sessions in linked worktrees at other paths count too. Codex threads attached as session peers are not counted; they are Codex runs, not Claude Code sessions.

The states come from Claude Code's per-session registry (~/.claude/sessions/*.json, the same internal file the @handle uses), and ? is a hint read from the end of each idle session's transcript, not a guarantee. Every session in the repo shows the same total, but not always the same split: a session waiting on you counts itself as ○ while the others count it as ?, so with two idle sessions where only A asked something, A shows [○2] and B shows ?1 [○1]. Each statusline also counts at its own redraw, so two sessions can briefly disagree by one; set refreshInterval on your statusLine so idle sessions redraw (60 seconds is plenty). Counts follow the directory and branch on phones. When space runs out, counts are dropped whole after Kubernetes context on wide layouts and first on phones. Only the upgrade notice stays right-aligned. Disable with STATUSLINE_PEERS=0.

Per-account usage fetcher

claude-usage-fetch.sh runs in the background (spawned by the statusline, throttled to once a minute across all your sessions per account) and asks api.anthropic.com/api/oauth/usage for the account's true 5h/7d usage, authenticated as the session itself: the session's CLAUDE_CODE_OAUTH_TOKEN when it was launched with one, your stored login otherwise. This exists because Claude Code feeds every session the same cached rate-limit numbers regardless of which account the session bills (shared ~/.claude.json state), so on multi-account machines the bars were whichever account fetched last. The credential is never logged, never put in argv/env, and is only sent to api.anthropic.com over HTTPS. Set STATUSLINE_RL_FETCH=0 to disable fetching. Existing per-account cache fallback remains available; account-keyed sessions never substitute Claude Code's account-agnostic native values.

If the usage endpoint refuses a credential (it answers 429 to CLAUDE_CODE_OAUTH_TOKEN credentials that the Messages API accepts), the fetcher falls back to a minimal Messages request (haiku, max_tokens: 1) and reads the account's limits off the anthropic-ratelimit-unified-* response headers. That probe costs a token or two of the account's quota; STATUSLINE_RL_PROBE=0 turns it off, and after a failure with no usable fallback the account backs off for STATUSLINE_RL_BACKOFF seconds.

A missing/null five_hour in a successful usage response, or an absent pair of 5h headers, means no 5h window: only 7d is shown. A genuine 0% window still renders. Partial/malformed windows or missing weekly data do not overwrite the previous valid cache. A fresh authoritative weekly-only snapshot hides 5h until its freshness TTL expires. After expiry, unkeyed sessions resume the existing freshness comparison with native data, so reopened windows and newer weekly usage can appear. Native weekly-only data replaces a two-window cache only once its cached 5h window has expired and the native weekly tuple is no older. Account-keyed sessions still use only their own fetched snapshots, including stale fallback. This is data-driven and does not assume a universal plan change.

GPT/Codex plan usage

GPT limits are off by default. Set STATUSLINE_GPT_LIMITS=1 in statusLine.command to use them when the effective model ID is gpt-* or a recognized OpenAI OAuth routed form:

"command": "STATUSLINE_GPT_LIMITS=1 cc-statusline"

codex-usage-fetch.sh starts the official Codex app server in the background and calls its read-only account/rateLimits/read method. The Codex CLI handles its own ChatGPT login, account selection, credential storage, and token refresh; cc-statusline never reads or receives the OAuth token and makes no inference request. Run codex login status to verify that CLI is logged in.

The response can contain only a weekly window, only a 5h window, or both. Windows are identified by duration rather than primary/secondary position, and the statusline renders whatever is available. The cache preserves each window's exact reported duration for pace_arrow. Codex reports a rolling duration and reset timestamp, and a live active reset stayed fixed while usage increased, but a reset-to-zero boundary has not been observed directly. The GPT arrow is therefore a projection heuristic for active windows. Zero-percent uninitialized/sliding snapshots produce no arrow. GPT data lives in a separate private cache and never enters the Claude cache. If the GPT snapshot is unavailable or older than STATUSLINE_GPT_AUTH_TTL, the rate segment is hidden rather than showing unrelated Claude percentages.

The line-2 service icon also switches providers. GPT sessions read only the exact Codex API component from status.openai.com/api/v2/components.json, cache it separately from Claude status, and link the icon to status.openai.com. Missing, duplicate, or malformed component data leaves the last valid Codex status untouched; Claude page status is never substituted.

GPT renders never show Claude Code's native $... cost because Claude Code applies Claude model prices to GPT token counts. Instead, gpt-credits-fetch.sh streams the current main transcript and its subagents/*.jsonl, deduplicates repeated assistant responses by message ID, and estimates GPT-5.6 Sol and GPT-6.1 Sol usage using published ChatGPT credit rates. Per million input, cached input, and output tokens, Standard rates are 100/10/500 credits for GPT-5.6 Sol and 50/2.5/250 for GPT-6.1 Sol. GPT-6.1 Sol Fast and Ultrafast use the published purchased-credit multipliers (2x and 6x); missing speed means Standard, while unknown speeds make the estimate unavailable. Only recognized bare or routed IDs for these models count; Claude, helper, opaque alias, and unknown model IDs are skipped. Mixed-model sessions use each response's own rates.

The display is a ChatGPT credit-equivalent usage estimate, not billed dollars and not proof that credits were deducted while Pro usage remained within its included limits. Codex credit billing has no separate cache-write charge; nevertheless, nonzero proxy cache_creation_input_tokens remains unsupported because its relationship to reported input tokens is unverified, so such a row makes the estimate unavailable. No API-style long-context multiplier is inferred. Fractional credit rates are accumulated before rounding the cache total to the nearest millionth of a credit. The private cache is keyed by transcript path and refreshes asynchronously at most once a minute. Zero usage stays hidden; positive estimates appear as 211.29 cr below 1,000 or 2.07k cr at four digits. Set STATUSLINE_GPT_CREDITS=0 to hide the estimate.

The same official Codex snapshot can also supply an available account credit balance, displayed separately as 54.91k cr, including 0 cr. With an estimate, for example, the display reads 211.29 cr · 54.91k cr: session usage estimate first, official available balance second, not a ratio or a subtraction. The balance follows STATUSLINE_GPT_LIMITS, not the estimate toggle, and needs no transcript or additional request. It follows the Codex CLI login, not necessarily the session router account. Missing, malformed, unlimited, future-dated, or stale balances are hidden; valid windows still render when credits are malformed. Finite balances must be nonnegative decimal strings with up to 18 integer and 18 fractional digits. The private cache retains the exact string, including high-precision balances such as 54906.0662120000; the display rounds to two decimals and compacts thousands and above (54.91k cr). Zero shows 0 cr, and a positive sub-cent balance shows <0.01 cr. A balance-only snapshot is supported. Both credit readouts are dropped in the phone layout. Old seven-field window caches remain readable; a fresh snapshot adds an optional eighth balance field to the same private atomic cache.

GPT sessions have been observed to report one context window with every usage counter at zero (used 0%, remaining 100%) between two normal readings. For a GPT effective model, independent of STATUSLINE_GPT_LIMITS, exactly that shape shows the session's last valid context percentage in dim gray instead of 0%, in both layouts; the capacity stays the current value. The last value is kept in a small private per-session file in the runtime directory. It is stored only when the transcript shows the matching assistant response after its last compact boundary, and it is used only while the session id, models, transcript path, and window size still match, the transcript is the same file (same device and inode, not shorter, and the last 4 KB before the recorded size unchanged; earlier edits to the same file are not detected, since the transcript is assumed to be append-only), and its last compact boundary is unchanged. A null or missing usage, any other zero-like shape, a /clear (new session id), a /compact, or a switch to a Claude model drops it, so a new session or one without a prior reading shows the native 0%. Genuine decreases, including a real 0% with input tokens, are always shown as reported.

Detection currently covers raw gpt-*, claude-ocx-native--gpt-*, clodex:openai-oauth:gpt-*, and anthropic-openai-oauth__gpt-* IDs, including a [1m] suffix. A short opaque alias such as sol cannot identify its provider by itself and is not detected. clodex:openai:* API-key routes are deliberately excluded because ChatGPT plan limits do not describe API-key usage.

Context export

Off by default. With STATUSLINE_CTX_EXPORT=1 (for example in the statusLine.command or the env block of settings.json), every render writes one JSON record for its session to ${XDG_STATE_HOME:-~/.local/state}/cc-statusline/ctx/<session_id>.json, so other local tools can read a session's context fill without parsing transcripts. The directory is created mode 700 and each record mode 600; it is replaced atomically (temporary file, then rename), so a reader never sees a partial record. The write is synchronous and local, adds no network or background process, and any failure is silent: the statusline still renders and the previous record stays in place.

One line of JSON, schema version 1:

{"v":1,"session_id":"5dd91f6e-954e-4692-8ff6-9cdd1940804d","used_pct":42,"used_tokens":415030,"context_window_size":1000000,"source":"native","stale":false,"ts":1791874402,"model_id":"claude-opus-5-5","effective_model_id":"claude-opus-5-5","cc_version":"2.1.296"}
Field Meaning
v Schema version. Bumped only on a breaking change; new fields may be added within a version, so ignore unknown fields.
session_id Claude Code's session id (also the file name). Ids outside [A-Za-z0-9._-] (max 128) are never written.
used_pct Context fill as displayed, integer 0 to 100.
used_tokens Input + cache-creation + cache-read tokens of the last response (output excluded), or null when not measured.
context_window_size Window size in tokens from Claude Code, or null when it sent no valid size.
source native (Claude Code's percentage), tokens (computed from token counts), held (a GPT pane re-showing its last value during an all-zero report), or unknown (no usable reading; used_pct is a display default).
stale true exactly when source is held.
ts Epoch seconds of this render (publication time, not when the API reported). Idle sessions keep advancing it on refreshInterval redraws.
model_id, effective_model_id Model on stdin and the one actually serving (they differ on GPT agent panes); "" when not a plain id.
cc_version Claude Code version, or "".

Treat used_pct as an exact current reading only when source is native or tokens and 0 <= now - ts < 180; a fresh held or unknown record is still not exact. Records are not a liveness signal: a file outlives its session. While the export is on, a best-effort sweep at most about once a day (throttled by a .gc stamp in the directory) deletes records not rewritten for at least seven days (the exact boundary follows the platform find -mtime rounding). A record being refreshed at the instant of a sweep can be deleted too; the next render of that session writes it again. After turning the export off, delete the directory yourself.

Update indicator

When a newer cc-statusline release is available, line 1 ends with a gold ⇡ X.Y.Z at its right edge, Cmd/Ctrl+clickable (OSC 8) to that release's GitHub page. Nothing is shown while you are current, so a fresh install looks exactly as before. Upgrade with brew upgrade cc-statusline (Homebrew) or ./install.sh --version vX.Y.Z (installer), and the indicator disappears on the next render.

cc-statusline-update-fetch.sh runs in the background at most once an hour per user (spawned by the statusline, throttled across all your sessions) and asks GitHub's "latest release" endpoint (api.github.com/repos/vtmocanu/cc-statusline/releases/latest, unauthenticated, no credential involved) for the newest tag, which it compares against the VERSION file installed next to the script. The indicator is placed in the padding line 1 already has under a wider line 2, so on a typical render it costs no columns; when line 1 is the wider line and appending it would breach the width budget, it is dropped for that render rather than truncated. Only a plain vMAJOR.MINOR.PATCH tag is accepted (checked in both the fetcher and the statusline), so a malformed or tampered response can never reach the terminal. Disable with STATUSLINE_UPDATE_CHECK=0.

Versioning

Releases are tagged with semantic version tags (v2.0.0, v2.0.1, v2.1.0, ...). The main branch is always the latest tested state. The 2.x line is continuous with the script's pre-public history (it lived in a private dotfiles repo as statusline-modern.sh); see CHANGELOG.md for details.

To roll back:

# Reinstall a specific version (does not touch your clone)
./install.sh --version v3.6.0

The archived release must include the helper scripts required by the current installer. Releases that predate those helpers need their own installer.

Testing

bash tests/run-tests.sh

# or, with go-task installed, the full validation suite (syntax + shellcheck + tests):
task ci

Runs the harness against the JSON fixtures in tests/fixtures/. Each fixture is piped through statusline.sh and asserted on:

  • exit code 0
  • exactly 2 stdout lines
  • visible columns within SAFE_WIDTH + WIDTH_SLOP (default 110 + 0)
  • empty stderr

CI runs the same harness on every push and pull request via .github/workflows/ci.yml, both under the default locale and under LC_ALL=C.

Troubleshooting

Symptom Likely cause Fix
Line 2 missing in Claude Code Line 1 exceeds the container width and cli-truncate drops subsequent lines Lower STATUSLINE_WIDTH (e.g. STATUSLINE_WIDTH=100)
Statusline not showing at all Script crash; set -uo pipefail exits silently Run STATUSLINE_DEBUG=1 bash statusline.sh < /tmp/test.json and check /tmp/statusline-debug.log
Wrong project color Color hash collision or stale override Add the project root to ~/.claude/statusline-color-overrides.json
Service status icon missing claude-status-fetch.sh not yet run, or curl failed Wait 60s, or run the fetcher manually: bash claude-status-fetch.sh
Update indicator never shows / stale cc-statusline-update-fetch.sh not yet run (hourly), curl failed, or the per-user cache is stale Run it manually: bash cc-statusline-update-fetch.sh; it writes update-check in the state dir ($XDG_RUNTIME_DIR or $TMPDIR, cc-statusline-<uid>/)
Session title empty Claude Code hasn't named the session yet (brand-new session), or you're on a Claude Code without .session_name Send a few prompts so a title is generated, or set one with /rename; upgrade Claude Code if the field is absent
@handle missing Registry file absent (older Claude Code), or its shape changed Upgrade Claude Code; the handle reads ~/.claude/sessions/*.json best-effort
Boxes/squares instead of icons Terminal font is not a Nerd Font Install one from nerdfonts.com and configure your terminal

See KNOWN_ISSUES.md for known limitations.

Why two lines?

Claude Code's cli-truncate silently drops line 2 when a line exceeds the container width. This script measures visible columns with an ANSI-aware helper (it does not estimate) and truncates content (in priority order: K8s context, branch, agent, mode, title, session name, directory) before that happens. See the comments in statusline.sh for the full set of undocumented rendering quirks discovered through testing.

License

MIT, see LICENSE.

About

Two-line ANSI statusline for Claude Code: git + k8s context, rate-limit bars, service health, AI session topics

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages