What Is Statusline? A Complete Guide to Claude Code’s Status Line, Configuration, and Customization

What Is Statusline

What Is Statusline?

Statusline is a customizable, always-visible information bar rendered at the bottom of the Claude Code terminal. It surfaces runtime context such as the active working directory, the current Git branch, the Claude model in use, the cumulative session cost, and the remaining context window — giving developers an at-a-glance view of an AI agent session that would otherwise live in scattered logs and mental state.

Think of it as a status bar like the one in VS Code, but reimagined for a terminal-based AI agent. Or, if you live in the shell, think of it as PS1 for Claude Code: a single configurable line that updates as your session evolves. The key idea is that you, the developer, write a small script that reads structured data on standard input and prints exactly one line on standard output. That line becomes the status line. This minimalism is intentional — it lets you turn the bottom of your terminal into a personal dashboard for AI-assisted coding without learning a new SDK. In practice, the most common uses are tracking spending in real time, surfacing which model you happen to be talking to, and verifying that the agent is operating in the directory you actually intended.

How to Pronounce Statusline

STAY-tus-lyne (/ˈsteɪtəsˌlaɪn/)

STAT-us-lyne (/ˈstætəsˌlaɪn/) — common in some regional accents

status line (two words) — used interchangeably in writing

How Statusline Works

Statusline is a rendering component that lives inside the Claude Code CLI process. On startup — and on every reload of settings during a session — Claude Code reads the user-level ~/.claude/settings.json and the project-level .claude/settings.json and looks for a statusLine entry. The configured command is invoked whenever Claude Code’s internal state changes meaningfully (after a tool run completes, after the user submits a message, after the model is switched, and so on). The last line of the command’s standard output is what gets rendered at the bottom of the terminal.

If multiple settings files are present, project-level configuration overrides user-level configuration on a per-key basis. This is how you can keep one Statusline for your day-to-day work in ~/.claude/settings.json and override it inside a particular repository to surface project-specific information. Note that you should always commit project-level settings to the repository so collaborators benefit from the same dashboard.

Pipeline

From settings.json to the bottom of your terminal

1) Load settings
settings.json
2) Pipe JSON into command
via stdin
3) Capture stdout
last line wins
4) Render at bottom of TUI

Important: Claude Code passes the current session as a JSON document on standard input. Your script reads that JSON, extracts the parts you care about, and writes a single line to stdout. The mechanism is intentionally minimal — any language that can read stdin and write stdout will work, which means existing internal tooling, monitoring scripts, and CI helpers can be reused without rewriting.

Common JSON fields available to your script

Field Description
workspace.current_dir Active working directory
model.display_name Active Claude model name
session_id Unique session identifier
cost.total_cost_usd Cumulative session cost in USD
output_style.name Currently selected Output Style
version Claude Code version string

Treat the JSON shape as evolving — Anthropic adds fields over time, and you should defensively use .get() or equivalent so a future field rename does not crash your status line. Keep in mind that you do not control when Claude Code calls your script, so it must be idempotent and safe to invoke many times per minute.

Statusline Usage and Examples

The simplest way to enable a status line is to add a statusLine entry to ~/.claude/settings.json. You should start small and grow the script as your needs become clearer. Below are progressively richer examples you can adapt directly.

Minimal Bash example showing the Git branch

{
  "statusLine": {
    "type": "command",
    "command": "bash -c 'dir=$(basename "$PWD"); branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo no-git); echo "📁 $dir | 🌿 $branch"'"
  }
}

This snippet does not even read the JSON on stdin — it relies on shell-level state. That is fine for a first iteration, but you will quickly want to surface model and cost from the structured input. Note that you should keep external commands inexpensive and resilient to failure.

Python example surfacing model and cost

#!/usr/bin/env python3
# ~/.claude/statusline.py
import sys, json, os

data = json.loads(sys.stdin.read())
dir_short = os.path.basename(data["workspace"]["current_dir"])
model = data["model"]["display_name"]
cost = data.get("cost", {}).get("total_cost_usd", 0)
print(f"📁 {dir_short} | 🤖 {model} | 💰 ${cost:.3f}")

Wire it up by pointing command at the script:

{
  "statusLine": {
    "type": "command",
    "command": "python3 ~/.claude/statusline.py"
  }
}

Important: keep in mind that command paths should be absolute (or anchored at $HOME). Relative paths break when Claude Code starts in a different working directory than you expect, and remember that the script must terminate quickly. The interpreter (here python3) must also be on the PATH that Claude Code inherited, so a virtualenv-only Python will silently fail.

Node.js example with a cost threshold

#!/usr/bin/env node
// ~/.claude/statusline.mjs
const chunks = [];
process.stdin.on("data", c => chunks.push(c));
process.stdin.on("end", () => {
  const data = JSON.parse(Buffer.concat(chunks).toString());
  const cost = data?.cost?.total_cost_usd ?? 0;
  const model = data?.model?.display_name ?? "unknown";
  const dir = (data?.workspace?.current_dir ?? "").split("/").pop();
  const flame = cost > 5 ? "🔥" : cost > 1 ? "⚠️" : "💰";
  process.stdout.write(`${dir} | ${model} | ${flame} $${cost.toFixed(2)}\n`);
});

This version emits a different emoji depending on accumulated cost. You should treat this kind of visual threshold as low-cost insurance; engineers respond to changing icons faster than to changing numbers.

Controlling refresh cost

Claude Code redraws the status line on internal state changes — after a tool runs, after a user message is submitted, etc. There is no fixed polling interval, but if your script takes hundreds of milliseconds, the entire CLI will feel sluggish. Note that you should keep total runtime under roughly 50 ms in practice. For Git lookups in large repositories, prefer lightweight commands such as git symbolic-ref --short HEAD over git status --porcelain. Avoid network calls entirely; if you need a remote signal, cache it from a separate cron job.

Advantages and Disadvantages of Statusline

Advantages

  • Situational awareness — You always see which directory and model you are using; this prevents the “wrong project” and “wrong model” bugs that plague AI-assisted coding sessions. Important when juggling many repos in parallel.
  • Cost visibility — Real-time USD cost is invaluable on metered plans like Claude Max and the Anthropic API. Note that emojis or color thresholds can flag overspending instantly without forcing engineers to read decimals.
  • Language freedom — Anything that reads stdin and writes stdout will do. Bash, Python, Node.js, Go, Rust — pick what your team is already comfortable maintaining.
  • Team-shareable — Commit .claude/settings.json to your repository and every contributor gets the same heads-up display, lowering the barrier for new joiners and standardizing how the team observes agent sessions.
  • No daemon required — Statusline is a one-shot child process, not a long-running daemon. There is nothing extra to monitor, restart, or upgrade.
  • Composable with Hooks — Statusline shows state, Hooks change behavior. Together they let you both observe and react to what the agent is doing.

Disadvantages

  • Performance traps — Network calls, slow Git operations, or shell-spawn-heavy scripts can noticeably slow Claude Code’s responsiveness. You should profile your script before and after every change.
  • Environment fragility — Dependencies on jq, Python, or specific shells will fail on machines that lack them. Defensive scripts use 2>/dev/null and sane fallbacks for every external invocation.
  • Width constraints — One physical line is all you get. Important to keep total length under ~80 columns and prefer emojis or short paths over verbose text.
  • One-line debugging — When the line is wrong, you cannot inspect intermediate state from inside Claude Code itself. You will end up running the script manually with a fixture to debug.
  • JSON shape drift — Anthropic may add or rename fields. Scripts that index without defaults will break on upgrade. Note that you should always wrap field access in defensive accessors.

Statusline vs Shell Prompt (PS1)

Developers often confuse Statusline with traditional shell prompts (Bash PS1, Zsh PROMPT, Fish fish_prompt). Both display contextual info, but they are very different beasts.

Aspect Statusline Shell prompt (PS1)
Host Claude Code (AI agent) Bash / Zsh / Fish
Input Structured JSON via stdin Environment variables, escape sequences
Position Bottom of TUI (always visible) Inline, before each input
Purpose Surface AI agent state Help you type commands
Lifecycle Re-runs on Claude state changes Re-runs before every prompt

In practice you keep both. PS1 stays focused on Git and shell ergonomics; Statusline focuses on Claude-specific signals. The two complement each other rather than compete. Important to remember: when you are using Claude Code, your shell prompt is hidden, and Statusline becomes your primary “where am I” indicator.

Common Misconceptions

Misconception 1: Statusline is interactive

It is not. Statusline only renders text — it cannot accept clicks or keystrokes. For interactive workflows (commands you can invoke from the chat), use Slash Commands instead. For automated reactions to events (e.g. running a linter after every edit), use Hooks. Statusline is purely about observability.

Misconception 2: You must use Bash

Wrong. Any executable that reads stdin and writes stdout will work. Pick the language your team already maintains, and keep in mind that less-common languages still need an interpreter installed on every developer’s machine. A 30-line Python script is often easier to maintain than a one-liner shell pipeline that works on macOS but breaks on Linux.

Misconception 3: Statusline always slows down Claude Code

Half true at best. A trivial script (string formatting, a single Git ref read) costs single-digit milliseconds. Heavy work — fetching from a remote API, scanning a large filesystem — is what causes the slowdown. Important: profile your script before blaming the feature, and if you must do something expensive, do it asynchronously (write a cache file from cron, then read the cache from Statusline).

Misconception 4: There can be only one Statusline

Strictly speaking, yes — only one command runs at a time. But the command itself can fan out to multiple sources, concatenate their outputs, and emit a single composite line. That is how teams build “dashboards” with Git, kubectl context, Docker status, and Claude state all on the same line.

Real-World Use Cases

Cost dashboards on Claude Max

Teams running Claude Max or directly billing the Anthropic API often display total_cost_usd with a color threshold (green < $5, yellow < $20, red ≥ $20). This single change has saved many engineering teams from accidental overspend at the end of the month. Important: refresh on every tool call so the number is never stale, and consider rolling 24-hour totals if you want to enforce daily budgets.

Multi-model session management

When juggling Opus 4.6, Sonnet 4.6, and Haiku 4.5 — or comparing against external models via routers — the model name on the status line prevents “I thought I was on Sonnet” mistakes. Pair this with Hooks to enforce a model per project type. You should treat the model display as a first-class signal during code review of the agent’s diffs.

Monorepo navigation

In monorepos, the same directory tree can host many sub-projects. A Statusline that prints the nearest package.json name (or Bazel target) helps the developer (and the agent) verify they are in the right place before applying changes. You should treat this as cheap insurance: a wrong-directory edit is the most common kind of agent regression.

Kubernetes context safety

Engineers who let Claude Code run kubectl or terraform commands often surface the current Kubernetes context and Terraform workspace on the status line. The visual feedback is a critical safety net — you really do not want the agent applying changes to production by accident, and a giant red prod badge on the bar is a strong deterrent.

Pair-programming visibility

When recording a session for documentation or review, the status line acts as a constantly-visible label that frames every screenshot. Note that you should add session start time so the recording’s chronology is unambiguous later.

Frequently Asked Questions (FAQ)

Q1. Where do I configure Statusline?

In ~/.claude/settings.json for user-wide settings, or .claude/settings.json at the project root. Project-level configs take precedence, which is exactly what you want for repo-specific dashboards. Important to remember: settings files are read at startup and on reload — restart Claude Code if changes are not visible.

Q2. Can I use colors and emojis?

Yes. Use ANSI escape codes (\033[31m) for color and any UTF-8 character for emojis. Note that some terminals render emojis at double width — keep in mind when budgeting your 80-column line. Test on the terminal each teammate actually uses; iTerm2, Alacritty, Windows Terminal, and the GNOME terminal differ in their emoji handling.

Q3. How often does it refresh?

It refreshes on internal state changes (tool runs, user prompts, model switches), not on a fixed timer. Important to remember if you expect a clock — implement that yourself in the rendered string instead, and accept that it will only update when something else triggers a redraw.

Q4. Is Statusline cross-platform?

Yes. Claude Code runs on macOS, Linux, and Windows (via WSL or native). Just keep in mind that any external commands you call must exist on the target OS, and that quoting rules differ between PowerShell and Bash. The most portable scripts use a single interpreter (Python or Node.js) and avoid shelling out for substitution.

Q5. What about errors?

If your script crashes or returns nothing, Claude Code falls back to a default minimal status line. You should pipe errors to /dev/null to keep the bar clean, but log to a file (tee -a ~/.claude/statusline.log) so you can debug after the fact. Silent failures are the most common Statusline bug.

Q6. Can I show multiple lines?

No. By design Statusline is a single line. If you need richer output, render a compact one-line summary and link to a detailed report file (or a tmux pane) that the developer can open separately. You should resist the urge to overload the line; legibility wins over completeness.

Q7. Does Statusline work with Claude Agent SDK?

The SDK builds custom agents that may or may not include a TUI. If you ship a CLI agent, you can implement your own status line; the conventions described here are specific to the Claude Code CLI. Note that you should reuse the same JSON schema where possible to keep your scripts portable.

Performance Engineering Tips

Because the Statusline command is invoked frequently, performance is the top operational concern after correctness. Important to remember: latency in your script becomes latency in Claude Code’s UI. The following techniques are battle-tested across teams running Statusline in production.

Cache aggressively. If you need a value that does not change between invocations (cluster name, Cloud account, GitHub user), compute it once and write it to a file in /tmp or $XDG_CACHE_HOME. Read from the cache on every Statusline call. You should refresh the cache from a separate cron job or shell hook.

Avoid forking. Each shell pipeline invocation forks a process. A four-pipe one-liner can take 30 ms on a slow Mac. A single Python script that does the same logic in-process often costs less than 5 ms. Important: prefer one runtime per Statusline call.

Bound the execution time. Wrap any external lookup in timeout 100ms (GNU coreutils) or its equivalent. If a lookup is slow, fall back to the cached value silently. Note that you should never let a stuck network call freeze the UI.

Profile honestly. Use time or hyperfine to measure. The bar between “feels fine” and “feels broken” is roughly 100 ms total — anything above that is a bug. Keep in mind that 50 ms on your machine may translate to 200 ms on a colleague’s Windows laptop, so leave headroom.

Security Considerations

Statusline scripts run with the same privileges as your interactive shell. That means a poorly written script is a privilege escalation vector inside your own session, not against the system, but still serious. You should treat your Statusline script with the same care as any other production code in your dotfiles.

The most common pitfall is injecting unsanitized data into shell commands. If your script reads workspace.current_dir and passes it to bash -c, a maliciously named directory can execute arbitrary code. Important: never interpolate JSON values into a shell string. Use exec-style invocations or pass values through process arguments instead.

The second pitfall is leaking secrets. Some teams display API keys, AWS profiles, or repository names that imply private codebases. Note that you should keep in mind that screen recordings, Zoom calls, and screenshots will preserve your status line forever.

Migrating From Other Tools

If you previously used Starship, Oh My Posh, or a custom powerline setup for your shell, much of your existing configuration translates directly. The signals you already wanted in your shell prompt — Git branch, Kubernetes context, virtualenv name, AWS profile — are exactly the signals you want in Statusline.

The mechanical translation is straightforward: keep the data sources you used, replace the prompt-rendering layer with a single Python or Node.js script, and emit one line. Important: do not try to share the same renderer between PS1 and Statusline; their performance budgets and update lifecycles differ enough that a shared renderer will compromise both.

Concretely, on macOS many engineers used to combine git, kubectl, and aws sts get-caller-identity in their Powerlevel10k segments. The same external commands fit cleanly into a Python Statusline; you call them, format the result with f-strings, and emit one terminal line. Note that you should expect modest latency improvements over Powerlevel10k because Statusline avoids reloading the shell environment between invocations. Important: when you migrate, port one signal at a time and verify total runtime stays under your performance budget — incremental migrations make regressions easy to bisect.

Future Directions

Anthropic continues to expand the JSON payload available to Statusline. Recent additions surface the active output style, the session context size, and per-tool invocation counts. Keep in mind that the trend is toward richer telemetry, so a defensive script — one that ignores unknown fields and gracefully handles missing ones — will age well. You should subscribe to the Claude Code release notes if you maintain a Statusline that is shared across an engineering organization.

Conclusion

  • Statusline is the customizable bar at the bottom of Claude Code’s TUI.
  • It reads a JSON document on stdin and prints one line to stdout — language-agnostic by design.
  • Configure it via statusLine in settings.json (user or project level).
  • Common signals: directory, Git branch, model, cumulative cost, output style.
  • Keep scripts fast (sub-50 ms) and resilient to missing dependencies.
  • Pair it with PS1 rather than replacing PS1 — they solve different problems.
  • Treat it as a low-cost, high-leverage way to make your AI sessions safer and cheaper.

References

📚 References