CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-05-02Intermediate

Recipes for a Useful Claude Code Status Line — Show Model, Remaining Context, and Git Branch in One Line

Concrete recipes for a Claude Code status line that displays the active model, remaining context, and git branch in a single line — written in both Bash and Node.js, with the failure modes you should plan for.

claude-code129status-linecustomization4productivity18settings7

After a long Claude Code session, the questions I keep asking myself are mundane but real: which model am I running right now, how much context is left before the next compact, and what branch am I on? Claude Code has a feature designed exactly for this — the status line that sits below the prompt — but the default presentation is so understated that many people scroll past the setting without realizing how much it can do.

I split my time across four properties and constantly toggle between Sonnet and Opus, so an unmissable model indicator turns out to be more than cosmetic — it keeps me from quietly burning Opus pricing on conversational chitchat. In this article I want to share the recipes I've actually settled on: what's worth showing in the status line, and how to write the script so it never breaks the conversation. Both Bash and Node.js versions are included.

What the status line actually is

Claude Code's status line is, mechanically, very simple: you point it at any executable, and Claude Code calls it with session metadata as JSON on stdin. Whatever your script writes to stdout becomes the rendered line — including ANSI escape codes for color and weight. There's no plugin API to learn; you just write a one-shot program that reads JSON in and prints a string out.

You wire it up by adding a statusLine block to ~/.claude/settings.json (or a project-local .claude/settings.json):

{
  "statusLine": {
    "type": "command",
    "command": "/Users/you/.claude/statusline.sh",
    "padding": 0
  }
}

type is currently always command, the command itself should be an absolute path, and padding: 0 gives your script full control over surrounding whitespace.

Why bother customizing it at all

This sounds like the dotfile-fiddler territory of "make the prompt fancier," but in practice I found three concrete reasons it pays off.

The first is avoiding accidental model spend. If you flip between Opus for hard reasoning tasks and Sonnet for everything else, an Opus-shaped bill at the end of the month is a nasty surprise. A persistent on-screen reminder of which model is loaded almost completely eliminates the "wait, was I still on Opus?" moment. The second is earlier signals on context exhaustion: somewhere past 80% used, conversations get noticeably wobbly, and a percent indicator on the status line is much easier to glance at than counting tokens manually. The third is branch awareness, which matters a lot if you work in multiple worktrees of the same repo.

If none of these apply, a heavy custom status line can actually backfire. Too many widgets pull your eyes away from the conversation itself, which is the thing you actually want to focus on.

The minimum useful Bash recipe

Let's start with a single Bash file that has no external dependencies beyond jq. This is the fastest path to a working setup.

#!/usr/bin/env bash
# ~/.claude/statusline.sh
# Called by Claude Code; reads session JSON from stdin, writes one line to stdout.
set -eu
 
# 1) Read the JSON Claude Code feeds us
INPUT="$(cat)"
 
# 2) Pull out only the fields we need (use the Node.js version if jq is unavailable)
MODEL_ID="$(echo "$INPUT" | jq -r '.model.id // "unknown"')"
MODEL_NAME="$(echo "$INPUT" | jq -r '.model.display_name // .model.id // "unknown"')"
CTX_USED="$(echo "$INPUT" | jq -r '.context.input_tokens // 0')"
CTX_LIMIT="$(echo "$INPUT" | jq -r '.context.limit // 200000')"
CWD="$(echo "$INPUT" | jq -r '.workspace.current_dir // "."')"
 
# 3) Compute remaining context as a percentage
if [ "$CTX_LIMIT" -gt 0 ]; then
  USED_PCT=$(( (CTX_USED * 100) / CTX_LIMIT ))
else
  USED_PCT=0
fi
REMAIN_PCT=$(( 100 - USED_PCT ))
 
# 4) Current git branch (silently skipped if not available)
BRANCH=""
if command -v git >/dev/null 2>&1; then
  BRANCH="$(git -C "$CWD" symbolic-ref --short -q HEAD 2>/dev/null || true)"
fi
 
# 5) Decorate with ANSI and emit a single line
RESET=$'\033[0m'
DIM=$'\033[2m'
CYAN=$'\033[36m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
RED=$'\033[31m'
 
# Color the percentage by remaining (red < 30%, yellow < 60%, otherwise green)
if [ "$REMAIN_PCT" -lt 30 ]; then COLOR="$RED";
elif [ "$REMAIN_PCT" -lt 60 ]; then COLOR="$YELLOW";
else COLOR="$GREEN"; fi
 
LINE="${CYAN}${MODEL_NAME}${RESET}"
LINE="${LINE} ${DIM}|${RESET} ctx ${COLOR}${REMAIN_PCT}%${RESET}"
[ -n "$BRANCH" ] && LINE="${LINE} ${DIM}|${RESET} ${BRANCH}"
 
printf '%s' "$LINE"

Make it executable with chmod +x ~/.claude/statusline.sh, point settings.json at it, and you'll see a line like this:

Sonnet 4.5 | ctx 73% | feature/auth-refactor

In my experience, showing the percentage rather than the raw token count is what made this stick. A number like 47,231 doesn't trigger any reflexes, but a percentage maps directly to "I should probably /compact soon."

The Node.js version, for environments without jq

On corporate-issued machines and CI runners I sometimes don't have jq available, and adding it is a hassle. Node.js 18+ ships with everything needed, so a self-contained Node script is often easier to deploy.

#!/usr/bin/env node
// ~/.claude/statusline.mjs
// Reads session JSON from stdin and prints a single decorated line to stdout.
 
import { execSync } from "node:child_process";
 
async function main() {
  // Drain stdin
  const chunks = [];
  for await (const chunk of process.stdin) chunks.push(chunk);
  let input = {};
  try {
    input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
  } catch {
    // Empty or malformed input: emit nothing rather than corrupt the bar
    process.stdout.write("");
    return;
  }
 
  const modelName = input?.model?.display_name || input?.model?.id || "unknown";
  const used = Number(input?.context?.input_tokens || 0);
  const limit = Number(input?.context?.limit || 200000);
  const cwd = input?.workspace?.current_dir || process.cwd();
 
  const usedPct = limit > 0 ? Math.floor((used * 100) / limit) : 0;
  const remainPct = 100 - usedPct;
 
  // Try git branch — never let a failure crash the script
  let branch = "";
  try {
    branch = execSync("git symbolic-ref --short -q HEAD", {
      cwd,
      stdio: ["ignore", "pipe", "ignore"],
    })
      .toString()
      .trim();
  } catch {
    branch = "";
  }
 
  const c = (code, s) => `\x1b[${code}m${s}\x1b[0m`;
  const cyan = (s) => c(36, s);
  const dim = (s) => c(2, s);
  const color =
    remainPct < 30 ? 31 : remainPct < 60 ? 33 : 32; // red / yellow / green
 
  let line = `${cyan(modelName)} ${dim("|")} ctx ${c(color, `${remainPct}%`)}`;
  if (branch) line += ` ${dim("|")} ${branch}`;
 
  process.stdout.write(line);
}
 
main().catch(() => {
  // Swallow even unexpected failures so the status line never breaks rendering
  process.stdout.write("");
});

In settings.json, set command to something like node /Users/you/.claude/statusline.mjs. If you want to lean on the shebang, chmod +x the file and pass the absolute path directly.

Never let a failing script disturb the conversation

The detail that's easy to overlook in a recipe like this: a status-line script that fails should never propagate noise into Claude Code itself. Claude Code is pretty forgiving about non-zero exits and stderr, but a hung process or a slow external call will visibly stall the rendering, and that will feel like the editor itself is slow.

There are four habits I always keep in my scripts. First, wrap every external command in a try-style guard — your script should still emit a line on machines without git. Second, return an empty string on JSON parse failure instead of throwing; the very first invocation of a session sometimes carries an empty payload. Third, don't call remote APIs. A "current Anthropic credit" widget sounds nice until network latency makes the bar feel sluggish; have a separate cron-style script update a local file and just read that. Fourth, stay under a 50ms budget. Past that, the rendering starts to feel jittery in a way that's hard to articulate but easy to feel.

A small ASCII bar makes remaining context land faster

Once the percentage version becomes second nature, you may want a more visual cue. A 10-cell bar reads instantly without parsing digits.

# Convert a percentage into a 10-segment block bar
make_bar() {
  local pct="$1"
  local filled=$(( pct / 10 ))
  local empty=$(( 10 - filled ))
  local bar=""
  for ((i=0; i<filled; i++)); do bar="${bar}█"; done
  for ((i=0; i<empty; i++)); do bar="${bar}░"; done
  printf '%s' "$bar"
}
 
BAR="$(make_bar "$REMAIN_PCT")"
LINE="${LINE} ${DIM}[${RESET}${COLOR}${BAR}${RESET}${DIM}]${RESET}"

At 73% remaining you'd see something like ███████░░░. The compact decision becomes a glance instead of a calculation, which matters more than it sounds when you're deep in a long session.

Project-level overrides for sensitive repos

settings.json resolves in two layers: user-wide (~/.claude/settings.json) and per-project (<repo>/.claude/settings.json), with the project file winning when both are present. That makes it easy to install a louder status line in repos that need extra care.

The pattern I use is to add an environment-aware status line only in repositories that touch production. The script reads a project-level env var (something like CLAUDE_ENV) and tints the bar red whenever it sees prod, which has saved me a couple of "wait, am I about to do this on prod?" near-misses.

# ~/projects/your-app/.claude/settings.json (project-local)
{
  "statusLine": {
    "type": "command",
    "command": "/Users/you/.claude/statusline-prod-aware.sh",
    "padding": 0
  }
}

The principle here is keep the global default quiet, and let the projects that need attention shout. If every repo has a flashy status line, no status line is doing useful signaling anymore.

Pitfalls worth knowing about

Three pitfalls catch people out regularly when they first wire this up.

The first is path expansion. Setting command to ~/.claude/statusline.sh looks reasonable, but Claude Code does not always expand the tilde, and the script will silently fail to start. Use a full absolute path like /Users/you/.claude/statusline.sh.

The second is trailing newlines. A naive echo appends a newline that can break the inline rendering. Use printf '%s' in shell or process.stdout.write in Node, and never call a function that adds \n for you.

The third is lines that are too long. On narrow terminals, an over-long status line wraps to two rows and the layout collapses. Stay under 50–70 characters, and if a particular field could grow long (a long branch name, for instance), truncate with an ellipsis.

A 30-minute investment here repays itself surprisingly quickly across long sessions. My suggestion is to drop the Bash version above into ~/.claude/statusline.sh as-is, point settings.json at it, and then add fields one at a time as you notice yourself wanting them. That trial-and-adjust loop is what gets you to a layout you actually trust at a glance — far more reliably than designing the perfect line up front.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-06-18
When a Broken settings.json Stops Claude Code From Starting — Safe Mode and How to Split Your Config
How to find which config layer is broken when a settings.json syntax error stops Claude Code from starting, recover in minutes, and structure your settings so an automated pipeline can't quietly break itself.
Claude Code2026-05-21
Claude Code Hook `command timed out`: Timeout Settings and Split-Execution Patterns That Actually Work
Fix Claude Code's `command timed out` hook failure without just bumping the timeout. Includes practical split-execution, detached background jobs, and a settings.json layout that keeps your session fast.
Claude Code2026-05-06
Parallel Development with Claude Code Across Multiple Repos — What Three Simultaneous Projects Taught Me
Claude Code gets messy with multiple repos. Here are the CLAUDE.md strategies, session habits, and cost tips from three months of real parallel development.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →