A two-line statusline for Claude Code: model and context usage, rate-limit windows, git state, and session duration.
<agent> [Model | ███▒░░ 62% / 200K] [5h ███▓░ 71% ↻18:40] [7d ██▒░░ 45% ↻3d] 📂 my-project + 2 dirs
my-project ( main > +2 ~1) [wt:feature <- main] 🕐 14:30 (1h 12m)
Line 1
| Group | Shows |
|---|---|
| Agent name | Only for subagents (e.g. dotfiles-maintainer). Claude Code populates this for subagents only. |
[Model | bar % / window] |
Model name, context-usage bar, percent used, and context-window size. |
[5h ...] |
Claude.ai 5-hour rate-limit window, with a ↻HH:MM reset clock. Omitted entirely when unavailable (e.g. API-key auth). |
[7d ...] |
Claude.ai 7-day rate-limit window, with a ↻Nd reset marker. Opt-in — off by default. |
| Directory | Current folder, plus a + N dirs count for extra workspace roots. |
Line 2
| Group | Shows |
|---|---|
| Project | Project name (always rendered). |
| Git | Branch, then status: ↑ ahead, ⊞ untracked, ☑ staged-new, ~ modified, - deleted, ✓ clean. Untracked precedes staged, so the pair reads as the ?? → A progression across git add. |
| Worktree | [wt:feature <- main] when in a linked worktree. |
| Clock | Session start time and elapsed duration. |
Bars. Each cell carries four sub-levels (░ ▒ ▓ █), so a bar resolves to
width × 4 steps rather than whole cells only.
Colour ramps. The context bar escalates by absolute tokens on 1M-context
models (≥200K, ≥300K, ≥400K) and by percentage on 200K models (≥70%, ≥80%,
≥90%) — same three colours either way, plain below the lowest threshold. A ⚠
precedes the bar past 200k tokens. The [5h] and [7d] groups share one ramp:
plain <50%, pale yellow 50–84%, dark orange 85–89%, red ≥90%; brackets and reset
marker take a dimmed variant of the same colour, so each group reads as one
colour with the bar dominant.
| Needed for | |
|---|---|
jq |
Hard requirement. The renderer parses its session JSON with it. Without jq there is no statusline. apt install jq / brew install jq / winget install jqlang.jq. |
| A Nerd Font terminal | The icons (folder, branch, clock). Without one they render as tofu boxes. Get one from nerdfonts.com and select it in your terminal — or set nerd_font=false, which falls the git icons back to glyphs any font has. |
git |
The line-2 git group only. Everything else still renders without it. |
node |
The settings helper and slash commands only. The statusline itself renders on its built-in defaults without Node. |
bash |
Works on macOS's bash 3.2 — no bash 4+ syntax anywhere. On Windows you need a bash environment: WSL, Git Bash, MSYS2 or Cygwin — see Windows. |
Line endings matter. This repo ships a .gitattributes forcing LF on shell
scripts. If you clone on Windows with core.autocrlf=true and lose it, the
renderer dies with $'\r': command not found — CRLF is not survivable for a
script Claude Code execs. Keep .gitattributes.
git clone https://github.com/rinodrummer/statusline.git
cd statusline
./install.shThen restart Claude Code.
| Flag | Default | Meaning |
|---|---|---|
--mode=copy |
✅ default | Copies the files. The install survives this clone moving or being deleted. |
--mode=symlink |
Links back to this clone, so git pull updates the installed statusline. The clone must stay put. |
|
--config-dir <path> |
~/.claude |
Claude Code config dir to install into. |
--force |
Replace an existing, unrelated statusline. See below. |
The installer is additive and idempotent: it writes only the paths below plus
a single statusLine key, deletes nothing, and re-running is a no-op. It patches
settings.json through jq, so your existing keys (model, hooks, permissions)
survive untouched. If settings.json doesn't exist it is created; if it exists
but is invalid JSON, the installer refuses rather than overwrite it.
If you already have a statusline (oh-my-posh, a script of your own), the
installer will not take it from you: it deploys everything, then warns and skips
only the wiring, printing the statusLine.command value to set by hand if you
want it. Re-run with --force to replace it. Re-running over a statusline this
installer wrote is not a clobber and needs no flag — it updates in place.
What lands where:
$CONFIG_DIR/statusline-command.sh # the renderer
$CONFIG_DIR/scripts/claude/statusline-settings.js # the settings helper
$CONFIG_DIR/commands/statusline-*.md # the slash commands
$CONFIG_DIR/settings.json # statusLine.command patched in
The renderer is a bash script, so what Windows needs is a bash environment —
something providing a POSIX shell and the usual utilities. Any of these will do,
and ./install.sh detects which one you are in:
| Environment | uname -s |
How the statusline is wired |
|---|---|---|
| WSL / WSL2 | Linux |
POSIX: bash <path> |
| Git Bash (ships with Git for Windows) | MINGW64_NT-* |
Windows: absolute bash.exe path |
| MSYS2 | MSYS_NT-* / MINGW64_NT-* |
Windows: absolute bash.exe path |
| Cygwin | CYGWIN_NT-* |
Windows: absolute bash.exe path |
Git Bash is the easiest if you already have Git installed; WSL is the closest to a real Linux. Pick either.
Your terminal emulator is a separate question and does not matter here. mintty (what Git Bash, MSYS2 and Cygwin open by default), Windows Terminal, ConEmu, WezTerm — any of them runs the statusline, because the emulator draws characters and the bash environment above is what executes the script. The emulator only decides whether the Nerd Font icons and the ANSI colours look right, which is a font setting, not a compatibility one. If the icons come out as tofu boxes, see Without a Nerd Font.
MinGW-w64 on its own is not a bash environment. It is a compiler toolchain —
headers, import libraries and tools that, paired with GCC or LLVM, build native
Windows binaries. It ships no shell, so there is nothing there to run this script.
The MINGW64 in a Git Bash or MSYS2 prompt is easy to misread as MinGW-w64 doing
the work: it is an MSYS2 environment name, saying which toolchain's
/mingw64/bin is on PATH. The shell in that window is MSYS2's bash, and that is
what actually runs the statusline. So "MINGW64 works" is true, but only because a
MINGW64 window is Git Bash or MSYS2 — installing MinGW-w64 alone gets you
nowhere.
./install.sh handles all of these. They are worth knowing anyway, because each
one fails silently if you wire things up by hand.
Install jq first. Git Bash ships without it; MSYS2 and Cygwin have it in
their own package managers (pacman -S jq, or the Cygwin setup GUI). Otherwise:
winget install jqlang.jq # or: scoop install jq / choco install jqThen reopen your shell so jq.exe is on PATH.
bash is not a command on Windows. Claude Code on Windows is a native build:
it hands statusLine.command to cmd.exe, not to the shell you installed from.
Git for Windows only adds its cmd directory to PATH, which holds git.exe
and no shell — while
C:\Windows\System32\bash.exe, the WSL launcher, is on PATH on Windows 10 and
11 whether or not a distribution is installed. So the ordinary POSIX spelling
{ "statusLine": { "command": "bash /c/Users/you/.claude/statusline-command.sh" } }does not fail loudly — it quietly hands your statusline to WSL, which answers with an error on stderr and nothing on stdout. A blank statusline is what you see. The installer writes the absolute form instead:
{
"statusLine": {
"type": "command",
"command": "\"C:/Program Files/Git/bin/bash.exe\" \"C:/Users/you/.claude/statusline-command.sh\""
}
}Both halves are Windows paths with forward slashes, quoted for cmd.exe. If you
installed before this was handled, just re-run ./install.sh — it recognises its
own previous command and rewrites it in place, no --force needed.
If you were on an older version and everything looked switched off — no
context bar, no git group, no clock, just [Model] 📂 dir — that was a third
Windows-only bug, now fixed: jq.exe ends its lines with CRLF, and the stray
carriage return made every section read as disabled the moment a
.statusline-settings file existed. Pull and re-run ./install.sh.
On WSL, none of this applies. There uname says Linux, Claude Code and the
shell agree on POSIX paths, and the plain bash <path> spelling is correct.
Settings live in one JSON file, $CLAUDE_CONFIG_DIR/.statusline-settings
(default ~/.claude/.statusline-settings). Its full shape is in
statusline-settings.schema.json.
/statusline-options is the canonical way to set it:
/statusline-options # print the current configuration
/statusline-options --json # print it as JSON
/statusline-options weekly=true # turn the [7d] group on
/statusline-options style=verbose git=false
/statusline-options --reset # back to defaults (the badge is kept)
/statusline-options --reset=git,clock # reset only these
Same thing without the slash command:
node ~/.claude/scripts/claude/statusline-settings.js options weekly=true| Key | Values | Default | What it does |
|---|---|---|---|
style |
compact, verbose |
compact |
verbose spells the effort/caveman/ponytail indicators out as labels; compact renders them as badges. |
nerd_font |
true/false |
true |
Whether your terminal has a Nerd Font. false draws the git icons with glyphs any font has — see Without a Nerd Font. Not a gate: it hides nothing, it only changes how the git group draws. |
agent |
true/false |
true |
The agent label on line 1. |
context |
true/false |
true |
The bar/percent/window run inside the model bracket. false still renders a bare [Model] — the model name has no gate of its own. |
five_hour |
true/false |
true |
The [5h] group. |
weekly (= seven_day) |
true/false |
false |
The [7d] group. The one section defaulting off. |
advisor |
true/false |
true |
The [advisor:<model>] marker. |
effort |
true/false |
true |
The effort/thinking indicator. |
caveman |
true/false |
true |
The caveman-mode indicator. |
ponytail |
true/false |
true |
The ponytail-mode indicator. |
git |
true/false |
true |
The line-2 git group. The project name is not part of it and always renders. |
clock (= session_start_date) |
true/false |
true |
The line-2 clock group: icon, start time, duration. |
Booleans accept true/on/1 and false/off/0, case-insensitively
(agent=TRUE, style=VERBOSE), and are stored canonically. An invalid key or
value aborts the whole batch — nothing is written. A section set to false is
omitted with no separator or double space left behind.
Every key except style and nerd_font is a section: it gates one renderable
group, and false omits it. style and nerd_font gate nothing — they change
how things draw, not whether they draw.
A missing, unreadable, or malformed settings file degrades silently to the defaults above. That is deliberate: a bad config file must still render a statusline rather than break your prompt. Nothing validates the schema at runtime.
The git status icons are Nerd Font glyphs, which
live in Unicode's Private Use Area. Without the font installed they render as
tofu boxes (□). If you cannot install one, turn the icons off:
/statusline-options nerd_font=falseThe git group then draws with characters any ordinary font has:
The Nerd Font column is given as codepoints on purpose — printed here as glyphs, it would be tofu boxes for exactly the readers this section is for.
nerd_font=true |
nerd_font=false |
|
|---|---|---|
| branch (leads the group) | U+E725 | git |
| ahead | U+F176 | ↑ |
| staged (new file) | U+F046 | ^ |
| untracked | U+F196 | + |
| modified | U+F044 | ~ |
| deleted | U+F147 | - |
| clean | ✓ U+2713 |
✓ (unchanged) |
Colours are identical in both modes — only the glyph changes. The table is in
render order; read + → ^ → ↑ and they describe the lifecycle of a change:
+ is new, ^ is staged (rising), ↑ is already gone up.
Only the Private Use Area glyphs are swapped, not everything non-ASCII: ✓
(U+2713) and ↑ (U+2191) are ordinary Unicode that renders without a Nerd Font,
so ✓ is the same in both modes and ↑ is a real arrow rather than a caret.
There is no auto-detection, and that is deliberate: a terminal cannot be asked
which fonts it has, and nothing in TERM_PROGRAM or WT_SESSION distinguishes a
real glyph from a tofu box. So you declare it. The default is true, which keeps
the icons for everyone who never touches this.
This setting covers the git icons. The context-warning and clock icons are Nerd Font glyphs too and are not currently affected by it.
A badge is an arbitrary (ANSI-capable) string prepended to line 1 — profile
identity, useful to tell two Claude Code configs apart at a glance. It is
shown by options but not settable through it:
node ~/.claude/scripts/claude/statusline-settings.js badge "PROD"
node ~/.claude/scripts/claude/statusline-settings.js badge --clearbadge=... and --reset=badge are rejected on purpose, and a plain --reset
leaves the badge alone.
Install elsewhere with --config-dir:
./install.sh --config-dir ~/.claude-workAt runtime the renderer resolves its config dir in this order, first match wins:
| Precedence | Source | Who sets it |
|---|---|---|
| 1 | --config-dir <path> (or --config-dir=<path>) |
You, inside the statusLine.command string — see below. |
| 2 | $STATUSLINE_CONFIG_DIR |
You. The tool-agnostic override. |
| 3 | $CLAUDE_CONFIG_DIR |
Claude Code, automatically, when it runs against a non-default config dir. |
| 4 | $HOME/.claude, then $USERPROFILE/.claude |
Fallback probe: the first of those holding the file being looked up. Defaults to $HOME/.claude if neither does. |
The two env vars are not redundant. CLAUDE_CONFIG_DIR is Claude Code's own
— you rarely set it by hand; it is already correct when Claude Code runs, which
is why the statusline follows a non-default config dir for free.
STATUSLINE_CONFIG_DIR is this tool's, and it takes precedence: it is the
one to reach for if you drive the renderer yourself (another prompt, a status
bar, a test harness) with no Claude Code around to set anything, or if you want
the statusline pinned to a dir regardless of what Claude Code says.
Both are honoured by the renderer and by statusline-settings.js, in the
same order, so /statusline-options always reads and writes the very file being
rendered. Exporting either one is enough to move both.
Two different
--config-dirflags. The one in the table is the renderer's, and you never type it at a prompt — the renderer is exec'd by Claude Code, so the flag lives inside thestatusLine.commandstring insettings.json:"command": "bash ~/.claude/statusline-command.sh --config-dir ~/.claude-work". That pins the renderer to one dir at the top of the precedence list. The--config-dirin the Install section is a different flag on a different program: it tellsinstall.shwhere to deploy, once. Setting one does not imply the other.
One wrinkle worth knowing: the slash-command files hardcode the script path
node ~/.claude/scripts/claude/statusline-settings.js. That path is not affected
by --config-dir, so if you installed only to a different dir, nothing is
deployed at ~/.claude and /statusline-options fails with a module-not-found
error rather than doing the wrong thing quietly.
Which settings file the helper touches is a separate question, and that part is resolved from the environment — so with the script present, exporting either config-dir variable points the slash command at the right file:
export STATUSLINE_CONFIG_DIR=~/.claude-workOr skip the slash command and call the helper by its real path, which needs no environment at all:
node ~/.claude-work/scripts/claude/statusline-settings.js options./tests/run.sh # bats tests/bash + node --test tests/jsRequires bats-core
(sudo apt install -y bats / brew install bats-core) and Node.js.