Claude Code Statusline: Setup, Examples and Fixes
A Claude Code statusline is a small command that prints session information beneath the prompt. A useful one shows the current model, project, context usage and available limit information without turning the bottom of the terminal into a dashboard. This tutorial gives you a versioned Node.js example for Windows, macOS and Linux, plus sample JSON inputs you can use to check whether the numbers are correct.
Quick setup: save the statusline.cjs script below in your .claude folder, add a statusLine command to settings.json, then test the script with the fixtures before trusting its live display. It needs Node.js 18 or later, uses no external packages and makes no API calls.
Sonnet | client app | ctx 47% | sess $1.37 | 5h 24% | 7d 31% | cap 63%
The line above is fixture output, not a claim about a live Claude account. The complete sample includes a Windows project path containing spaces and deliberately separates six kinds of information. If a value is unavailable, the script displays -- or leaves the optional segment out rather than inventing a zero.
Read the numbers before deciding what to display
| Segment | What it reads | What it does not mean |
|---|---|---|
Sonnet | model.display_name | Not a promise that every subagent uses the same model. |
client app | Last folder in workspace.current_dir | Not the entire repository path or necessarily the directory where the session began. |
ctx 47% | context_window.used_percentage | Not 47% of a subscription allowance or monthly spend. |
sess $1.37 | cost.total_cost_usd | Not necessarily the amount charged to your card. |
5h 24%, 7d 31% | Available rate_limits windows | Not the size or fullness of the model’s context window. |
cap 63% | rate_limits.spend_limit.used_percentage | Not a universal API billing cap: this field is for applicable Claude apps gateway spend limits. |
The context percentage is calculated from input tokens, including cache reads and cache writes. It does not add the latest output tokens to the numerator. Claude Code reports its most recent API response, while /context can estimate extra content added since that response. A brief disagreement between the two displays is therefore possible.
Session cost is an API-equivalent estimate, usually calculated at list prices, and resets when /clear starts a new session. For Pro or Max subscribers, it is not an invoice and should not be treated as money deducted from a subscription balance. Our Claude Code pricing guide explains the practical difference between plan allowances, optional overage and metered API use.
Install one statusline script on Windows, macOS or Linux
First, run node --version in your terminal. The example below uses CommonJS so it runs without an npm installation step, TypeScript build, or jq. It reads a single JSON document from standard input and prints one line to standard output. No Git commands run on each refresh, which avoids unnecessary work in a large repository.
Step 1: Save DIY AI statusline v1.0
Copy the complete script into statusline.cjs. Use ~/.claude/statusline.cjs on macOS and Linux, or the corresponding .claude directory in your Windows user profile. Create the folder if it does not already exist. Keep the .cjs extension so Node treats the file as CommonJS even if your project defaults to ES modules.
// DIY AI statusline v1.0 - Node.js 18+, no packages or API calls.
'use strict';
function clean(value, fallback = '') {
return typeof value === 'string' && value.trim()
? value.replace(/[\x00-\x1f\x7f]/g, ' ').trim()
: fallback;
}
function short(value, max) {
const chars = Array.from(value);
return chars.length <= max ? value
: chars.slice(0, max <= 3 ? max : max - 3).join('') + (max <= 3 ? '' : '...');
}
function folder(value) {
return clean(value).replace(/[\\/]+$/, '').split(/[\\/]/).pop() || '';
}
function percent(value) {
return Number.isFinite(value) ? `${Math.round(value)}%` : '--';
}
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', () => {
let data;
try { data = JSON.parse(input); }
catch { console.error('statusline: invalid JSON on stdin'); process.exitCode = 1; return; }
const model = short(clean(data?.model?.display_name, 'Claude'), 16);
const dir = short(folder(data?.workspace?.current_dir ?? data?.cwd), 16);
const context = 'ctx ' + percent(data?.context_window?.used_percentage);
const dollars = data?.cost?.total_cost_usd;
const sessionCost = 'sess ' + (Number.isFinite(dollars) ? '$' + dollars.toFixed(2) : '--');
const five = data?.rate_limits?.five_hour?.used_percentage;
const seven = data?.rate_limits?.seven_day?.used_percentage;
const spend = data?.rate_limits?.spend_limit?.used_percentage;
let parts = [
{ key: 'model', text: model },
...(dir ? [{ key: 'project', text: dir }] : []),
{ key: 'context', text: context },
{ key: 'cost', text: sessionCost },
...(Number.isFinite(five) ? [{ key: 'five', text: '5h ' + percent(five) }] : []),
...(Number.isFinite(seven) ? [{ key: 'seven', text: '7d ' + percent(seven) }] : []),
...(Number.isFinite(spend) ? [{ key: 'spend', text: 'cap ' + percent(spend) }] : [])
];
const columns = Number.parseInt(process.env.COLUMNS || '', 10);
const budget = Math.max(12, (columns >= 12 ? columns : 80) - 6);
const width = () => parts.map(x => x.text).join(' | ').length;
for (const key of ['spend', 'project', 'seven', 'five', 'cost']) {
if (width() > budget) parts = parts.filter(x => x.key !== key);
}
if (width() > budget) {
const index = parts.findIndex(x => x.key === 'model');
if (index !== -1) parts[index].text = short(parts[index].text, Math.max(1, budget - context.length - 3));
}
process.stdout.write(parts.map(x => x.text).join(' | ') + '\n');
});
The code deliberately keeps the output to one line, avoids emojis and colours, and budgets six characters for interface spacing. It reads COLUMNS when Claude Code supplies the terminal width, then removes lower-priority segments in order: spend cap, folder, seven-day limit, five-hour limit and session cost. Model and context are preserved where possible. The width calculation uses JavaScript string length, so filenames containing double-width characters may still need a larger margin.
Step 2: Configure the command for your operating system
Open your user-level ~/.claude/settings.json. Add the statusLine property to the existing JSON object rather than replacing permissions, hooks or other settings. The padding value of zero avoids adding extra spaces to an already narrow terminal.
Windows (PowerShell or Git Bash): replace Your Name with your actual Windows profile folder name. Keep the forward slashes and the escaped quotation marks inside the JSON string. Claude Code can execute the command through Git Bash if installed, or PowerShell otherwise, and either route needs the script filename to stay one argument.
{
"statusLine": {
"type": "command",
"command": "node \"C:/Users/Your Name/.claude/statusline.cjs\"",
"padding": 0
}
}
macOS or Linux: use the shell-expanded home directory. You do not need to mark a .cjs file executable because Node runs it directly.
{
"statusLine": {
"type": "command",
"command": "node \"$HOME/.claude/statusline.cjs\"",
"padding": 0
}
}
Save the configuration. Claude Code normally reloads settings automatically; the statusline updates after the next relevant event. If you prefer the built-in generator, /statusline can create a different script from a natural-language description, but use the manual settings above when you want this exact version and testable output.
Test three input states before relying on the display
Create three JSON files in a temporary folder. These fixtures intentionally cover a populated session, an early session where values are null, and an object in which optional fields are missing. The first also establishes why a displayed context percentage must not be calculated by adding output tokens.
Fixture A: A populated session and a Windows folder containing spaces
Save as active.json. Its input tokens total 94,000 against a 200,000-token window, so the supplied 47% is internally consistent; the 1,000 output tokens are not added to that percentage.
{
"model": { "display_name": "Sonnet" },
"workspace": { "current_dir": "C:\\Users\\Alex Doe\\Projects\\client app" },
"context_window": {
"context_window_size": 200000,
"used_percentage": 47,
"current_usage": {
"input_tokens": 32000,
"cache_creation_input_tokens": 40000,
"cache_read_input_tokens": 22000,
"output_tokens": 1000
}
},
"cost": { "total_cost_usd": 1.37 },
"rate_limits": {
"five_hour": { "used_percentage": 24 },
"seven_day": { "used_percentage": 31 },
"spend_limit": { "used_percentage": 63 }
}
}
Fixture B: Context and cost are not available yet
Save as warming.json. A missing value is not the same as measured zero usage. Immediately after /compact, current_usage can also return to null until the next API response.
{
"model": { "display_name": "Opus" },
"workspace": { "current_dir": "/home/alex/my project" },
"context_window": { "used_percentage": null, "current_usage": null },
"cost": { "total_cost_usd": null },
"rate_limits": null
}
Fixture C: Optional objects are absent altogether
Save as missing.json. This is the regression input for a common broken implementation.
{ "model": { "display_name": "Opus" } }
From the folder holding your fixtures, test on macOS or Linux:
COLUMNS=80 node "$HOME/.claude/statusline.cjs" < active.json
COLUMNS=36 node "$HOME/.claude/statusline.cjs" < active.json
node "$HOME/.claude/statusline.cjs" < warming.json
node "$HOME/.claude/statusline.cjs" < missing.json
Or in Windows PowerShell, quote the script path exactly as configured:
$env:COLUMNS = '36'
Get-Content -Raw .\active.json | node "C:/Users/Your Name/.claude/statusline.cjs"
Get-Content -Raw .\warming.json | node "C:/Users/Your Name/.claude/statusline.cjs"
Remove-Item Env:COLUMNS -ErrorAction SilentlyContinue
For the supplied fixtures, the expected output is:
# active.json with 80 columns
Sonnet | client app | ctx 47% | sess $1.37 | 5h 24% | 7d 31% | cap 63%
# active.json with 36 columns
Sonnet | ctx 47% | sess $1.37
# warming.json
Opus | my project | ctx -- | sess --
# missing.json
Opus | ctx -- | sess --
These outputs check parsing, escaping and width selection without consuming Claude Code usage. The production line still depends on your installed Claude Code version, account type, and available session data; the fixture verifies only the formatter.
A real failure case: dereferencing a missing context object
A tempting first implementation reads data.context_window.used_percentage directly. It works on a complete example, then fails with a TypeError when context_window is absent. The statusline can disappear even though Claude Code itself is working. You can reproduce the problem with missing.json by running this intentionally broken expression against the parsed fixture:
const pct = Math.round(data.context_window.used_percentage); // TypeError
The correction is to check both the object and the numeric value before formatting it:
const value = data?.context_window?.used_percentage;
const label = Number.isFinite(value) ? `${Math.round(value)}%` : '--';
Using value || 0 instead is weaker. It turns a genuine unknown into 0%, making a new session appear empty rather than not yet measured. The v1.0 script uses the same guarded read for cost and rate-limit fields. Invalid JSON is handled separately: it writes an error to standard error and exits unsuccessfully rather than printing invented session data.
Fix a blank, clipped or misleading Claude Code statusline
| Symptom | Check first | Fix |
|---|---|---|
| Completely blank line | Run the exact node command with active.json. | Check Node is on the shell’s PATH, JSON settings are valid, and output goes to stdout. Use claude --debug for script errors. |
| Works in PowerShell but not inside Claude Code | Look for Windows backslashes or unquoted spaces in statusLine.command. | Use a quoted path with forward slashes as in the Windows example. Git Bash treats unquoted backslashes as escapes. |
ctx -- after startup or compaction | Check whether an API response has completed. | Send the next request and re-check. Do not force the field to zero or guess from transcript size. |
| Line wraps or interface layout shifts | Set COLUMNS=36 and inspect the formatter’s output. | Reduce segments or increase the reserved width margin. Claude Code’s own notifications can occupy part of the row. |
No 5h, 7d or cap | Check whether your account and gateway supply those objects. | Do not derive a quota percentage from session cost. Leave unsupported segments absent. |
| Statusline works in a trusted project but not another | Inspect workspace trust and managed hook restrictions. | Complete the trust flow or ask the administrator whether managed settings disable custom status lines. |
Before debugging output colours or adding git status, verify one plain line. Calling Git, checking a network endpoint or parsing an entire transcript on every redraw makes diagnosis harder, and a slow command can be cancelled when a new update arrives. Claude Code’s official statusline documentation describes update events, the JSON fields, Windows command handling and the debug checks.
See more of our reporting in Google Top Stories, AI Overviews and AI Mode.
What this statusline cannot tell you
A context window, a session cost estimate, a gateway spend cap, and a subscription rate limit are four different measurements. A model can have plenty of context remaining while your five-hour account allowance is nearly exhausted. Equally, a high context percentage may warrant /compact without suggesting any spend-limit problem.
For subscription accounts, the rate-limit fields may appear only after an API response, and individual windows may be omitted. A gateway spend limit is not automatically available to every API user. Avoid promising a precise reset time, live account balance or hard spending protection from a script that only reads the provided JSON. If you need billing controls, configure them in the billing or gateway system itself.
The best use of a statusline is early warning and orientation. Keep it concise, then use Claude Code best practices for the separate questions of task scoping, verification and controlling long-running agents. If you launch Claude Code from an IDE terminal, the same path and display checks apply; our JetBrains AI coding workflow guide covers the wider integration choice.
Preflight checklist before you keep the statusline
node --versionworks in the environment used by Claude Code.settings.jsonremains valid and retains existing settings.- Windows script paths use quoted forward slashes; folder names with spaces survive.
- Populated, null and missing-field fixtures all print their expected output.
- A 36-column fixture produces one line without wrapping.
- Unknown values remain
--, and absent account limits are not fabricated. - Live output is checked after a completed response and again after
/compact.
Start with v1.0 unchanged. Once it survives those checks, add one feature at a time and keep the three fixtures as a regression test. A smaller statusline whose numbers you can explain is more useful than a colourful one you cannot troubleshoot.


