Skip to content

Claude Code Status Line

Customize the Claude Code status bar with a script that displays context window usage, model name, working directory, and repository name with branch.

How it works

Add a statusLine entry to ~/.claude/settings.json that points to a script. Claude Code pipes a JSON object to the script's stdin on each render. The script prints a single line to stdout, which replaces the default status bar.

json
{
  "statusLine": {
    "type": "command",
    "command": "node ~/.claude/scripts/statusline.js"
  }
}

Available data

The JSON object piped to stdin includes:

FieldDescription
model.idModel identifier (e.g. claude-opus-4-6)
model.display_nameShort model name (e.g. Opus)
workspace.current_dirCurrent working directory
workspace.project_dirOriginal project directory
transcript_pathPath to the conversation transcript
context_window.context_window_sizeMax tokens (200k or 1M depending on model)
context_window.used_percentagePre-calculated context usage (0–100)
context_window.remaining_percentagePre-calculated context remaining (0–100)
cost.total_cost_usdSession cost so far
versionClaude Code version string

Some fields appear conditionally: vim (when vim mode is on), worktree (during worktree sessions), rate_limits (for Claude.ai subscribers after the first API response).

Reference implementation

Save to ~/.claude/scripts/statusline.js, then chmod +x that file.

Each segment is rendered by its own function and joined with spacing. Shows a 5-char context usage progress bar (color-coded by fill level), the model name, the working directory, and repoName@branch where repoName is parsed from the origin remote URL (so it reflects the actual repo name, not the CWD or worktree folder).

javascript
#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");

const RESET = "\x1b[0m";
const YELLOW = "\x1b[93m";
const RED = "\x1b[91m";

function renderContext(data) {
  const ctx = data.context_window ?? {};
  const rate = ctx.used_percentage ?? 0;

  const barLength = 5;
  const filledLength = Math.round(barLength * rate / 100);
  const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);

  let barColor = "";
  if (rate >= 70) barColor = RED;
  else if (rate >= 40) barColor = YELLOW;

  return `${barColor}${bar}${RESET} ${rate.toFixed(0)}%`;
}

function renderModel(data) {
  return `🧠 ${data.model.display_name}`;
}

function renderWorkingDir(data) {
  const currentDir = path.basename(data.workspace.current_dir);
  return `📁 ${currentDir}`;
}

function renderRepo(data) {
  let gitDir = null;
  let dir = data.workspace.current_dir;
  while (dir !== path.dirname(dir)) {
    const candidate = path.join(dir, ".git");
    if (fs.existsSync(candidate)) {
      const stat = fs.statSync(candidate);
      if (stat.isDirectory()) {
        gitDir = candidate;
      } else if (stat.isFile()) {
        try {
          const contents = fs.readFileSync(candidate, "utf8").trim();
          const match = contents.match(/^gitdir:\s*(.+)$/);
          if (match) gitDir = path.resolve(dir, match[1]);
        } catch {}
      }
      break;
    }
    dir = path.dirname(dir);
  }

  if (!gitDir) return "";

  let commonDir = gitDir;
  const commonDirFile = path.join(gitDir, "commondir");
  if (fs.existsSync(commonDirFile)) {
    try {
      const rel = fs.readFileSync(commonDirFile, "utf8").trim();
      commonDir = path.resolve(gitDir, rel);
    } catch {}
  }

  let branch = "";
  try {
    const ref = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
    if (ref.startsWith("ref: refs/heads/")) {
      branch = ref.replace("ref: refs/heads/", "");
    }
  } catch {}
  if (!branch) return "";

  let repoName = null;
  try {
    const config = fs.readFileSync(path.join(commonDir, "config"), "utf8");
    const match = config.match(/\[remote "origin"\][^\[]*?url\s*=\s*([^\n]+)/);
    if (match) {
      repoName = match[1].trim().replace(/\.git$/, "").split(/[:/]/).pop();
    }
  } catch {}
  if (!repoName) repoName = path.basename(path.dirname(commonDir));

  return `🔀 ${repoName}@${branch}`;
}

let input = "";
process.stdin.on("data", chunk => input += chunk);
process.stdin.on("end", () => {
  const data = JSON.parse(input);
  const parts = [
    renderContext(data),
    renderModel(data),
    renderWorkingDir(data),
    renderRepo(data),
  ].filter(Boolean);
  process.stdout.write(parts.join("   ") + "\n");
});

Disabling temporarily

To revert to the default status bar without losing the script, remove the statusLine entry from ~/.claude/settings.json. The script stays on disk and can be re-enabled by adding the entry back.

Sync

Install the status line script and settings entry on the current machine. Follow this procedure exactly.

1. Install script

Create ~/.claude/scripts/ if it doesn't exist. Write the reference implementation above to ~/.claude/scripts/statusline.js. Make it executable.

2. Add settings entry

Read ~/.claude/settings.json. If a statusLine entry already exists, compare it to the recommended config above. If missing or different, show the user the current and recommended values. Ask whether to update. Apply if confirmed.

3. Preserve other settings

When writing ~/.claude/settings.json, only modify the statusLine key. Preserve all other keys and use 2-space JSON indentation.