# Claude Code Status Line: Show Context, Cost and Limits

> Set up a Claude Code status line that shows context fill, session cost, plan limits and cache health, with a working bash script and fixes for a blank line.

Canonical: https://bridge-terminal.com/atlas/blog/claude-code-status-line-context-cost-limits/
Last updated: 2026-10-27

Published 27 October 2026 · Technical · Explainer · 8 min read

## The short version

- A Claude Code status line is a script you configure. It receives session data as JSON on stdin and prints a row above the footer. The docs say it runs locally and does not consume API tokens.
- Set it up with the /statusline command, or add a statusLine entry with type command to settings.json.
- The JSON includes context-window percentage, estimated session cost, 5-hour and 7-day plan usage (Pro and Max only) and prompt cache health (v2.1.251 or later).
- Guard every optional field, keep the script fast, and cache slow git calls by session_id. A non-zero exit or empty output blanks the line.
- Session cost is a client-side estimate at list price. On Pro and Max it is not relevant for billing, so use it as a guide.

A Claude Code status line is a row in the terminal that runs a script you choose and shows whatever the script prints. Claude Code passes the script session data as JSON, so you can keep context fill, session cost, plan limits and git state in view without typing a command. Per the docs, it runs locally and does not consume API tokens.

This guide covers setup, the fields worth showing, a working script, and the mistakes that leave the line blank or stale. It is based on the official status line and cost documentation.

## What is the Claude Code status line?

The status line renders in its own row above the built-in footer badges. It does not replace them, but once a custom status line is configured, Claude Code stops showing most of the footer's keyboard hints, including `esc to interrupt`.

Your script reads JSON on stdin and writes text to stdout. Each line you print becomes a row. You can use ANSI colors, and OSC 8 escape sequences for clickable links in terminals that support them (iTerm2, Kitty, WezTerm).

Three reasons to use one:

- **Context fill.** You see how full the window is before a compaction fires.
- **Which session is which.** With several sessions open, the line can show the branch, worktree or session name.
- **Limits and spend.** Plan usage and cost sit next to your prompt instead of behind a command.

## How do I set up a status line?

There are two routes.

### Option 1: let Claude write it

Run `/statusline` with a plain-language request. Claude Code generates a script in `~/.claude/` and updates your settings:

```text
/statusline show model name and context percentage with a progress bar
```

Approve the file edit prompts if Claude Code asks for permission.

### Option 2: configure it by hand

Add a `statusLine` field to `~/.claude/settings.json` (user settings) or to project settings. Set `type` to `"command"` and point `command` at a script:

```json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 2
  }
}
```

The `command` runs in a shell, so an inline command works too. This one uses `jq` to print the model and context percentage:

```json
{
  "statusLine": {
    "type": "command",
    "command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
  }
}
```

There is also a `refreshInterval` setting that re-runs the command on a fixed timer. You need it for clocks, or when the line depends on something that changes while the main session is idle.

### A minimal working script

Save this as `~/.claude/statusline.sh`:

```bash
#!/bin/bash
input=$(cat)

MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)

echo "[$MODEL] ${DIR##*/} | ${PCT}% context"
```

Then run `chmod +x ~/.claude/statusline.sh`. Claude Code reloads settings automatically and runs the script as soon as you save the settings file.

To test without starting a session, pipe in mock input:

```bash
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test-session-abc"}' | ~/.claude/statusline.sh
```

## What data can the status line show?

The JSON is larger than most people expect. These fields are the most useful day to day:

| Field | What it gives you |
| - | - |
| `model.id`, `model.display_name` | Current model |
| `workspace.current_dir` | Where you are |
| `workspace.git_worktree`, `worktree.*` | Linked worktree details; only present inside one |
| `context_window.used_percentage` | Percent of the context window used |
| `cost.total_cost_usd` | Estimated session cost |
| `cost.total_duration_ms`, `cost.total_api_duration_ms` | Session time, and time spent waiting on the API |
| `rate_limits.five_hour`, `rate_limits.seven_day` | `used_percentage` and `resets_at` for each plan window |
| `prompt_cache.*` | Cache warmth, hit ratio, misses |
| `session_name`, `agent.name`, `pr.*` | Session label, active agent, open PR or merge request |

Two details matter for accuracy:

- `used_percentage` is calculated from input tokens only (`input_tokens + cache_creation_input_tokens + cache_read_input_tokens`). Output tokens are not included. `/context` can read higher because it adds an estimate for messages added since the last API response.
- `cost.total_cost_usd` is computed locally from token counts at list price, unless an administrator has set a `modelPricing` table in managed settings. The cost docs call it an estimate. They also say that on Pro and Max the session cost figure is not relevant for billing, because usage is included in the subscription. The total resets to $0 when `/clear` starts a new session.

### Plan limits only appear for some accounts

`rate_limits` is present only for claude.ai Pro and Max subscribers (or behind a Claude apps gateway with a spend limit), and only after the first API response in a session. Each window can be absent on its own, and Claude Code drops a window once its `resets_at` time passes. A script that assumes the field exists will misbehave on API-key setups.

### Cache health

The `prompt_cache` object needs Claude Code v2.1.251 or later and appears after the main conversation's first API response. It includes `warm`, `ttl` (`"5m"` or `"1h"`), `expires_at`, `hit_ratio` (0 to 1) and `misses`. From v2.1.260 it also reports a likely cause for the last miss, such as `tools_changed`, `system_prompt_changed` or `ttl_expired_5m`. The same statistics appear on the `Prompt cache (main)` line of `/usage`. Subagent requests are not counted.

A miss means a request re-processed content the cache already held. In the docs' definition, that is more than 5% and at least 2,000 tokens of what could have been read from cache, with no compaction or tool-result clearing to explain it.

## A status line for context, cost and limits

This script puts a colored context bar, cost and plan usage on one row, and copes with absent fields:

```bash
#!/bin/bash
input=$(cat)

MODEL=$(echo "$input" | jq -r '.model.display_name')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
if [ "$PCT" -ge 90 ]; then C="$RED"
elif [ "$PCT" -ge 70 ]; then C="$YELLOW"
else C="$GREEN"; fi

FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
printf -v FILL "%${FILLED}s"; printf -v PAD "%${EMPTY}s"
BAR="${FILL// /#}${PAD// /-}"

LIMITS=""
[ -n "$FIVE_H" ] && LIMITS=" | 5h $(printf '%.0f' "$FIVE_H")%"
[ -n "$WEEK" ] && LIMITS="$LIMITS 7d $(printf '%.0f' "$WEEK")%"

echo -e "[$MODEL] ${C}${BAR}${RESET} ${PCT}% | $(printf '$%.2f' "$COST")${LIMITS}"
```

The color thresholds (green under 70%, yellow 70 to 89%, red from 90%) match the docs' multi-line example; adjust them to taste. The `// empty` idiom prints nothing when a field is missing, so the limits segment disappears for accounts without `rate_limits`.

## How often does the status line run?

It runs once when a session starts or resumes, then again when:

- a new assistant message arrives
- `/compact` finishes
- the permission mode changes
- vim mode toggles
- the `command` in your settings changes
- a `refreshInterval` timer elapses, if you set one
- a rate-limit window reaches its `resets_at` time, or a warm prompt cache reaches its `expires_at` time

Updates are debounced at 300 ms, except that a change to the command itself runs right away. If a new update triggers while your script is still running, Claude Code cancels the in-flight run. The event-driven triggers can go quiet when the main session is idle, for example while a coordinator waits on background subagents. Set `refreshInterval` if the line must stay current then.

## Common problems and fixes

**The line is blank.** Check these in turn:

- The script is not executable (`chmod +x`).
- It writes to stderr instead of stdout.
- It exits non-zero or prints nothing. Claude Code displays output only after the script exits with code 0.
- You have not accepted the workspace trust dialog for the folder. Until then the line stays blank, and `claude --debug` logs `Status line command skipped: workspace trust not accepted`.
- `disableAllHooks` is `true` outside managed settings, in which case only a `statusLine` from managed settings runs. Or your organization sets `allowManagedHooksOnly`, which means you can only get a status line from managed settings.

`claude --debug` logs your script's stderr on every invocation and its exit code on the first one in a session, which makes it the quickest way to find a parse error.

**Values show `--` or empty.** Fields can be `null` before the first API response. `context_window.used_percentage` may be `null` early in a session, and `context_window.current_usage` is `null` again right after `/compact` until the next API call. Use fallbacks such as `// 0`.

**The line lags.** The script runs often, and `git status` or `git diff` in a large repo is slow. Cache the result in a temp file for a few seconds. Key the cache on `session_id` from the JSON, not on `$$`: a process ID changes on every invocation and defeats the cache, while `session_id` is stable within a session and unique across concurrent ones. The docs include a full example that refreshes git info every 5 seconds.

**Links are not clickable.** Terminal.app does not support them. If your terminal does but the link text is not clickable, Claude Code may not have detected hyperlink support; launch with `FORCE_HYPERLINK=1 claude`. SSH and tmux can strip the escape sequences. If escape codes show up as literal text, use `printf '%b'` instead of `echo -e`.

**Windows paths.** With Git Bash, write paths in `command` with forward slashes. Unquoted backslashes are treated as escape characters, and the command fails without a visible error.

**Garbled output.** Complex escape sequences, especially in multi-line status lines, can occasionally corrupt the display. Simplify to plain text if you see it.

## Status lines for subagents

A separate `subagentStatusLine` setting customizes the row shown for each subagent in the agent panel. Its command runs once per refresh tick and receives all visible rows as one JSON object, with a `tasks` array whose entries include fields such as `name`, `status`, `model`, `tokenCount` and `contextWindowSize`. You answer with one JSON line per row, in the form `{"id": "<task id>", "content": "<row body>"}`. Omit `id` to keep the default row; send an empty `content` to hide it. The same trust, `disableAllHooks` and `allowManagedHooksOnly` gates apply as for `statusLine`. If you split work across helpers, this is a cheap way to see which one is using the most tokens.

## Should the status line be shared with a team?

Because `statusLine` executes a shell command, it falls under the same workspace-trust rule as hooks in settings files. A project-level status line therefore runs code from the repository on whoever trusts the folder. Keep shared scripts small and reviewable, and prefer user settings for personal preferences. Plugins can ship a default `subagentStatusLine`, but unlike hooks, those values do not run under `allowManagedHooksOnly`.

## Conclusion

A status line is a small script that keeps context fill, estimated cost and plan usage in front of you, so you can compact or start fresh before a session gets expensive. Start with `/statusline`, move to a hand-written script once you know which fields you read, guard every optional field, and cache anything slow. Treat the cost figure as an estimate; for authoritative billing, the cost docs point to the Usage page in the Claude Console.

## Questions people ask

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

Run /statusline with a description of what you want, and Claude Code writes the script and updates your settings. Or add a statusLine entry with type set to command and a command pointing at your script to ~/.claude/settings.json.

### Does the Claude Code status line use tokens?

No. The docs say the status line runs locally and does not consume API tokens. It does run often during active sessions, so keep the script fast.

### Why is my Claude Code status line blank?

Common causes are a script that is not executable, one that writes to stderr or exits non-zero, an unaccepted workspace trust dialog, or the disableAllHooks or allowManagedHooksOnly settings. Run claude --debug to see the script's stderr.

### Can the status line show my plan usage?

Yes, for claude.ai Pro and Max subscribers. The rate_limits field gives used_percentage and resets_at for the 5-hour and 7-day windows, but only after the first API response in a session. It is absent on other account types.

### Is the cost in the status line my real bill?

No. It is an estimate computed locally at list price, unless an administrator has set a modelPricing table. On Pro and Max the docs say the session cost figure is not relevant for billing, because usage is included in the subscription.

## About Atlas: Watching context and plan usage across a whole team

A status line shows one session's context and limits. Atlas runs each engineer as its own Claude Code session, and its Network screen shows what every engineer is doing live. An optional daily budget warns at 50% and 80% and pauses at 100%. Per-engineer context fill and plan usage are coming to Atlas, built in, so they need no setup. Until then, set a status line in your own Claude Code sessions. — https://bridge-terminal.com/atlas/

## Sources

- [Customize your status line - Claude Code Docs](https://code.claude.com/docs/en/statusline)
- [Manage costs effectively - Claude Code Docs](https://code.claude.com/docs/en/costs)

_Researched and drafted with Claude, then checked against the sources above. Atlas is an independent product, not affiliated with Anthropic or OpenAI._

## Related

- [CLAUDE.md Best Practices: What to Put in Your Project File](https://bridge-terminal.com/atlas/blog/claude-md-best-practices-project-instructions/)
- [Claude Code OpenTelemetry: Track Cost and Tool Use](https://bridge-terminal.com/atlas/blog/claude-code-opentelemetry-monitor-cost-usage/)
- [Claude Code settings.json: Scopes and Precedence Explained](https://bridge-terminal.com/atlas/blog/claude-code-settings-json-scopes-precedence/)
