Files
claude-statusline/statusline.md
Kaj Kowalski f1ca1ccaa0 Use go-git for native git operations
Replace exec-based git calls with go-git/v6 library for ~5.6x
speedup over shell version. Only pgrep remains as external call.
2025-12-18 06:36:55 +01:00

7.5 KiB

Status line configuration

Create a custom status line for Claude Code to display contextual information

Make Claude Code your own with a custom status line that displays at the bottom of the Claude Code interface, similar to how terminal prompts (PS1) work in shells like Oh-my-zsh.

Create a custom status line

You can either:

  • Run /statusline to ask Claude Code to help you set up a custom status line. By default, it will try to reproduce your terminal's prompt, but you can provide additional instructions about the behavior you want to Claude Code, such as /statusline show the model name in orange

  • Directly add a statusLine command to your .claude/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 0 // Optional: set to 0 to let status line go to edge
  }
}

How it Works

  • The status line is updated when the conversation messages update
  • Updates run at most every 300 ms
  • The first line of stdout from your command becomes the status line text
  • ANSI color codes are supported for styling your status line
  • Claude Code passes contextual information about the current session (model, directories, etc.) as JSON to your script via stdin

JSON Input Structure

Your status line command receives structured data via stdin in JSON format:

{
  "hook_event_name": "Status",
  "session_id": "abc123...",
  "transcript_path": "/path/to/transcript.json",
  "cwd": "/current/working/directory",
  "model": {
    "id": "claude-opus-4-1",
    "display_name": "Opus"
  },
  "workspace": {
    "current_dir": "/current/working/directory",
    "project_dir": "/original/project/directory"
  },
  "version": "1.0.80",
  "output_style": {
    "name": "default"
  },
  "cost": {
    "total_cost_usd": 0.01234,
    "total_duration_ms": 45000,
    "total_api_duration_ms": 2300,
    "total_lines_added": 156,
    "total_lines_removed": 23
  },
  "context_window": {
    "total_input_tokens": 15234,
    "total_output_tokens": 4521,
    "context_window_size": 200000,
    "current_usage": {
      "input_tokens": 8500,
      "output_tokens": 1200,
      "cache_creation_input_tokens": 5000,
      "cache_read_input_tokens": 2000
    }
  }
}

Example Scripts

Simple Status Line

#!/bin/bash
# Read JSON input from stdin
input=$(cat)

# Extract values using jq
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')

echo "[$MODEL_DISPLAY] 📁 ${CURRENT_DIR##*/}"

Git-Aware Status Line

#!/bin/bash
# Read JSON input from stdin
input=$(cat)

# Extract values using jq
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')

# Show git branch if in a git repo
GIT_BRANCH=""
if git rev-parse --git-dir > /dev/null 2>&1; then
    BRANCH=$(git branch --show-current 2>/dev/null)
    if [ -n "$BRANCH" ]; then
        GIT_BRANCH=" | 🌿 $BRANCH"
    fi
fi

echo "[$MODEL_DISPLAY] 📁 ${CURRENT_DIR##*/}$GIT_BRANCH"

Python Example

#!/usr/bin/env python3
import json
import sys
import os

# Read JSON from stdin
data = json.load(sys.stdin)

# Extract values
model = data['model']['display_name']
current_dir = os.path.basename(data['workspace']['current_dir'])

# Check for git branch
git_branch = ""
if os.path.exists('.git'):
    try:
        with open('.git/HEAD', 'r') as f:
            ref = f.read().strip()
            if ref.startswith('ref: refs/heads/'):
                git_branch = f" | 🌿 {ref.replace('ref: refs/heads/', '')}"
    except:
        pass

print(f"[{model}] 📁 {current_dir}{git_branch}")

Node.js Example

#!/usr/bin/env node

const fs = require("fs");
const path = require("path");

// Read JSON from stdin
let input = "";
process.stdin.on("data", (chunk) => input += chunk);
process.stdin.on("end", () => {
  const data = JSON.parse(input);

  // Extract values
  const model = data.model.display_name;
  const currentDir = path.basename(data.workspace.current_dir);

  // Check for git branch
  let gitBranch = "";
  try {
    const headContent = fs.readFileSync(".git/HEAD", "utf8").trim();
    if (headContent.startsWith("ref: refs/heads/")) {
      gitBranch = ` | 🌿 ${headContent.replace("ref: refs/heads/", "")}`;
    }
  } catch (e) {
    // Not a git repo or can't read HEAD
  }

  console.log(`[${model}] 📁 ${currentDir}${gitBranch}`);
});

Helper Function Approach

For more complex bash scripts, you can create helper functions:

#!/bin/bash
# Read JSON input once
input=$(cat)

# Helper functions for common extractions
get_model_name() { echo "$input" | jq -r '.model.display_name'; }
get_current_dir() { echo "$input" | jq -r '.workspace.current_dir'; }
get_project_dir() { echo "$input" | jq -r '.workspace.project_dir'; }
get_version() { echo "$input" | jq -r '.version'; }
get_cost() { echo "$input" | jq -r '.cost.total_cost_usd'; }
get_duration() { echo "$input" | jq -r '.cost.total_duration_ms'; }
get_lines_added() { echo "$input" | jq -r '.cost.total_lines_added'; }
get_lines_removed() { echo "$input" | jq -r '.cost.total_lines_removed'; }
get_input_tokens() { echo "$input" | jq -r '.context_window.total_input_tokens'; }
get_output_tokens() { echo "$input" | jq -r '.context_window.total_output_tokens'; }
get_context_window_size() { echo "$input" | jq -r '.context_window.context_window_size'; }

# Use the helpers
MODEL=$(get_model_name)
DIR=$(get_current_dir)
echo "[$MODEL] 📁 ${DIR##*/}"

Context Window Usage

Display the percentage of context window consumed. The context_window object contains:

  • total_input_tokens / total_output_tokens: Cumulative totals across the entire session
  • current_usage: Current context window usage from the last API call (may be null if no messages yet)
    • input_tokens: Input tokens in current context
    • output_tokens: Output tokens generated
    • cache_creation_input_tokens: Tokens written to cache
    • cache_read_input_tokens: Tokens read from cache

For accurate context percentage, use current_usage which reflects the actual context window state:

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

MODEL=$(echo "$input" | jq -r '.model.display_name')
CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size')
USAGE=$(echo "$input" | jq '.context_window.current_usage')

if [ "$USAGE" != "null" ]; then
    # Calculate current context from current_usage fields
    CURRENT_TOKENS=$(echo "$USAGE" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
    PERCENT_USED=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE))
    echo "[$MODEL] Context: ${PERCENT_USED}%"
else
    echo "[$MODEL] Context: 0%"
fi

Tips

  • Keep your status line concise - it should fit on one line
  • Use emojis (if your terminal supports them) and colors to make information scannable
  • Use jq for JSON parsing in Bash (see examples above)
  • Test your script by running it manually with mock JSON input: echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/test"}}' | ./statusline.sh
  • Consider caching expensive operations (like git status) if needed

Troubleshooting

  • If your status line doesn't appear, check that your script is executable (chmod +x)
  • Ensure your script outputs to stdout (not stderr)

To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt