This guide teaches you how to create custom status lines for Claude Code that display contextual information at the bottom of your interface, similar to terminal prompts in shells like Oh-my-zsh.
Status lines are customizable information displays that appear at the bottom of the Claude Code interface. Each status line:
- Shows contextual information about your current session, model, directory, and project
- Updates automatically when conversation messages change (max every 300ms)
- Supports ANSI colors and emojis for rich visual formatting
- Receives session data as JSON input for dynamic content generation
- Runs custom scripts that you create and configure
Display current model, directories, git status, and project metrics at a glance.
Use colors, emojis, and formatting to create informative and visually appealing status displays.
Status line refreshes automatically as your session progresses and context changes.
Write custom scripts in any language to display exactly the information you need.
Access rich session data including costs, duration, model info, and workspace details.
You can set up a status line in two ways:
Use the built-in command for guided setup:
/statuslineThis will help you create a status line, often reproducing your terminal prompt by default.
With custom instructions:
/statusline show the model name in orange and git branch in green
/statusline include current time and session cost
/statusline make it minimal with just directory and modelAdd directly to your settings file:
File: .claude/settings.json
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 0
}
}Configuration Options:
type: Always"command"(only supported type currently)command: Path to your status line script (absolute or relative to home directory)padding: Optional spacing from edges (default has padding, set to0for edge-to-edge)
| Type | Location | Scope | Usage |
|---|---|---|---|
| User Settings | ~/.claude/settings.json |
All projects | Personal status line across all projects |
| Project Settings | .claude/settings.json |
Current project | Team-shared status line (committed) |
| Local Project Settings | .claude/settings.local.json |
Current project | Personal project status line (not committed) |
Your status line script receives comprehensive session data via stdin:
{
"hook_event_name": "Status",
"session_id": "abc123-def456-789",
"transcript_path": "/Users/you/.claude/projects/my-project/transcript.jsonl",
"cwd": "/Users/you/projects/my-project",
"model": {
"id": "claude-3-5-sonnet-20241022",
"display_name": "Sonnet"
},
"workspace": {
"current_dir": "/Users/you/projects/my-project/src",
"project_dir": "/Users/you/projects/my-project"
},
"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
}
}model.id: Full model identifiermodel.display_name: Human-readable model name (Sonnet, Haiku, Opus)
workspace.current_dir: Current working directoryworkspace.project_dir: Original project root directorycwd: Current working directory (same as workspace.current_dir)
cost.total_cost_usd: Total API cost in USDcost.total_duration_ms: Total session durationcost.total_api_duration_ms: Total API request timecost.total_lines_added: Lines of code addedcost.total_lines_removed: Lines of code removed
session_id: Unique session identifiertranscript_path: Path to conversation transcriptversion: Claude Code versionoutput_style.name: Current output style
statusline.sh:
#!/bin/bash
# Essential status line with model, directory, and git info
# Read JSON input from stdin
input=$(cat)
# Extract core information using jq
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name')
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PROJECT_DIR=$(echo "$input" | jq -r '.workspace.project_dir')
# Get directory name (relative to project if possible)
if [[ "$CURRENT_DIR" == "$PROJECT_DIR"* ]]; then
REL_DIR="${CURRENT_DIR#$PROJECT_DIR}"
REL_DIR="${REL_DIR#/}"
DISPLAY_DIR="${REL_DIR:-$(basename "$PROJECT_DIR")}"
else
DISPLAY_DIR=$(basename "$CURRENT_DIR")
fi
# Git branch information
GIT_INFO=""
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
if [ -n "$BRANCH" ]; then
# Check for uncommitted changes
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
GIT_STATUS="±"
else
GIT_STATUS="✓"
fi
GIT_INFO=" \033[32m🌿 $BRANCH$GIT_STATUS\033[0m"
fi
fi
# Output with colors
echo -e "\033[94m[$MODEL_DISPLAY]\033[0m \033[93m📁 $DISPLAY_DIR\033[0m$GIT_INFO"dev-statusline.py:
#!/usr/bin/env python3
import json
import sys
import os
import subprocess
from datetime import datetime, timedelta
def get_git_info():
"""Get comprehensive git information"""
if not os.path.exists('.git'):
return ""
try:
# Current branch
branch = subprocess.check_output(['git', 'branch', '--show-current'],
stderr=subprocess.DEVNULL).decode().strip()
if not branch:
return ""
# Check for changes
status_cmd = ['git', 'status', '--porcelain']
status_output = subprocess.check_output(status_cmd, stderr=subprocess.DEVNULL).decode().strip()
if status_output:
# Count changes
lines = status_output.split('\n')
modified = sum(1 for line in lines if line.startswith(' M'))
added = sum(1 for line in lines if line.startswith('A'))
deleted = sum(1 for line in lines if line.startswith(' D'))
untracked = sum(1 for line in lines if line.startswith('??'))
changes = []
if modified: changes.append(f"~{modified}")
if added: changes.append(f"+{added}")
if deleted: changes.append(f"-{deleted}")
if untracked: changes.append(f"?{untracked}")
status_indicator = f"({','.join(changes)})" if changes else "±"
else:
status_indicator = "✓"
return f" \033[32m🌿 {branch}{status_indicator}\033[0m"
except subprocess.CalledProcessError:
return ""
def format_duration(ms):
"""Format duration in human readable format"""
seconds = ms / 1000
if seconds < 60:
return f"{seconds:.0f}s"
elif seconds < 3600:
minutes = seconds / 60
return f"{minutes:.1f}m"
else:
hours = seconds / 3600
return f"{hours:.1f}h"
def format_cost(cost_usd):
"""Format cost in readable format"""
if cost_usd < 0.01:
return f"{cost_usd*100:.1f}¢"
else:
return f"${cost_usd:.3f}"
def get_node_version():
"""Get Node.js version if available"""
try:
if os.path.exists('package.json'):
version = subprocess.check_output(['node', '--version'],
stderr=subprocess.DEVNULL).decode().strip()
return f" \033[32m⬢ {version}\033[0m"
except (subprocess.CalledProcessError, FileNotFoundError):
pass
return ""
def get_python_version():
"""Get Python version if available"""
try:
if os.path.exists('requirements.txt') or os.path.exists('pyproject.toml'):
version = subprocess.check_output(['python', '--version'],
stderr=subprocess.DEVNULL).decode().strip()
version = version.replace('Python ', '')
return f" \033[33m🐍 {version}\033[0m"
except (subprocess.CalledProcessError, FileNotFoundError):
pass
return ""
def main():
# Read JSON from stdin
data = json.load(sys.stdin)
# Extract basic information
model = data['model']['display_name']
current_dir = data['workspace']['current_dir']
project_dir = data['workspace']['project_dir']
# Calculate relative directory
if current_dir.startswith(project_dir):
rel_dir = current_dir[len(project_dir):].lstrip('/')
display_dir = rel_dir if rel_dir else os.path.basename(project_dir)
else:
display_dir = os.path.basename(current_dir)
# Session metrics
cost = data['cost']
duration_str = format_duration(cost['total_duration_ms'])
cost_str = format_cost(cost['total_cost_usd'])
# Lines changed
lines_info = ""
if cost['total_lines_added'] > 0 or cost['total_lines_removed'] > 0:
lines_info = f" \033[36m📝 +{cost['total_lines_added']}/-{cost['total_lines_removed']}\033[0m"
# Build status line
status_parts = [
f"\033[94m[{model}]\033[0m",
f"\033[93m📁 {display_dir}\033[0m",
get_git_info(),
get_node_version(),
get_python_version(),
lines_info,
f"\033[90m⏱ {duration_str} • {cost_str}\033[0m"
]
# Filter empty parts and join
status_line = "".join(part for part in status_parts if part.strip())
print(status_line)
if __name__ == "__main__":
main()minimal-statusline.sh:
#!/bin/bash
# Minimal, clean status line
input=$(cat)
# Extract essentials
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
DIR_NAME=$(basename "$DIR")
# Simple git branch
BRANCH=""
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH_NAME=$(git branch --show-current 2>/dev/null)
if [ -n "$BRANCH_NAME" ]; then
BRANCH=" • $BRANCH_NAME"
fi
fi
echo "$MODEL • $DIR_NAME$BRANCH"performance-statusline.js:
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Read JSON from stdin
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const data = JSON.parse(input);
// Extract information
const model = data.model.display_name;
const currentDir = data.workspace.current_dir;
const projectDir = data.workspace.project_dir;
const cost = data.cost;
// Calculate directory display
const displayDir = currentDir.startsWith(projectDir)
? currentDir.slice(projectDir.length + 1) || path.basename(projectDir)
: path.basename(currentDir);
// Performance metrics
const avgResponseTime = cost.total_api_duration_ms / Math.max(1, cost.total_duration_ms / 10000);
const efficiency = cost.total_lines_added / Math.max(0.01, cost.total_cost_usd * 100);
// Git status (cached for performance)
let gitInfo = '';
try {
if (fs.existsSync('.git/HEAD')) {
const headContent = fs.readFileSync('.git/HEAD', 'utf8').trim();
if (headContent.startsWith('ref: refs/heads/')) {
const branch = headContent.replace('ref: refs/heads/', '');
gitInfo = ` \x1b[32m⭐ ${branch}\x1b[0m`;
}
}
} catch (e) {
// Ignore git errors
}
// Build status with performance focus
const parts = [
`\x1b[94m[${model}]\x1b[0m`,
`\x1b[93m📁 ${displayDir}\x1b[0m`,
gitInfo,
`\x1b[36m⚡ ${efficiency.toFixed(0)} lines/¢\x1b[0m`,
`\x1b[90m${avgResponseTime.toFixed(0)}ms avg\x1b[0m`
];
console.log(parts.filter(p => p.trim()).join(' '));
} catch (error) {
console.log(`[Status Error] ${error.message}`);
}
});project-statusline.py:
#!/usr/bin/env python3
import json
import sys
import os
import subprocess
from pathlib import Path
def detect_project_type():
"""Detect project type and return appropriate icon and info"""
if os.path.exists('package.json'):
with open('package.json', 'r') as f:
try:
package = json.load(f)
framework = ""
if 'react' in str(package.get('dependencies', {})):
framework = "⚛️"
elif 'vue' in str(package.get('dependencies', {})):
framework = "💚"
elif 'angular' in str(package.get('dependencies', {})):
framework = "🅰️"
elif 'express' in str(package.get('dependencies', {})):
framework = "🚂"
else:
framework = "📦"
name = package.get('name', 'unknown')
version = package.get('version', '0.0.0')
return f"{framework} {name}@{version}"
except:
return "📦 Node.js"
elif os.path.exists('Cargo.toml'):
try:
with open('Cargo.toml', 'r') as f:
content = f.read()
# Simple parsing for name and version
name = "rust-project"
version = "0.1.0"
for line in content.split('\n'):
if line.strip().startswith('name ='):
name = line.split('=')[1].strip().strip('"')
elif line.strip().startswith('version ='):
version = line.split('=')[1].strip().strip('"')
return f"🦀 {name}@{version}"
except:
return "🦀 Rust"
elif os.path.exists('go.mod'):
try:
with open('go.mod', 'r') as f:
first_line = f.readline().strip()
if first_line.startswith('module '):
module_name = first_line.split(' ')[1].split('/')[-1]
return f"🐹 {module_name}"
except:
return "🐹 Go"
elif os.path.exists('pyproject.toml') or os.path.exists('requirements.txt'):
if os.path.exists('pyproject.toml'):
try:
import toml
with open('pyproject.toml', 'r') as f:
data = toml.load(f)
name = data.get('project', {}).get('name', 'python-project')
version = data.get('project', {}).get('version', '0.1.0')
return f"🐍 {name}@{version}"
except:
return "🐍 Python"
else:
return "🐍 Python"
elif os.path.exists('pom.xml'):
return "☕ Java/Maven"
elif os.path.exists('build.gradle') or os.path.exists('build.gradle.kts'):
return "☕ Java/Gradle"
elif os.path.exists('Gemfile'):
return "💎 Ruby"
elif os.path.exists('composer.json'):
return "🐘 PHP"
return "📁 Generic"
def get_testing_status():
"""Check testing status"""
test_files = []
for pattern in ['*test*', '*spec*', 'tests/', '__tests__/', 'test/']:
test_files.extend(Path('.').glob(pattern))
if test_files:
return " \033[32m🧪\033[0m"
return ""
def get_docker_status():
"""Check if Docker is configured"""
if os.path.exists('Dockerfile') or os.path.exists('docker-compose.yml'):
return " \033[36m🐳\033[0m"
return ""
def get_ci_status():
"""Check CI configuration"""
ci_files = ['.github/workflows/', '.gitlab-ci.yml', '.travis.yml', 'Jenkinsfile']
for ci_file in ci_files:
if os.path.exists(ci_file):
return " \033[35m🔄\033[0m"
return ""
def main():
data = json.load(sys.stdin)
# Basic info
model = data['model']['display_name']
current_dir = data['workspace']['current_dir']
project_dir = data['workspace']['project_dir']
# Directory display
if current_dir.startswith(project_dir):
rel_dir = current_dir[len(project_dir):].lstrip('/')
display_dir = rel_dir if rel_dir else os.path.basename(project_dir)
else:
display_dir = os.path.basename(current_dir)
# Project detection
project_info = detect_project_type()
# Git info
git_info = ""
if os.path.exists('.git'):
try:
branch = subprocess.check_output(['git', 'branch', '--show-current'],
stderr=subprocess.DEVNULL).decode().strip()
if branch:
git_info = f" \033[32m🌿 {branch}\033[0m"
except:
pass
# Additional status indicators
test_status = get_testing_status()
docker_status = get_docker_status()
ci_status = get_ci_status()
# Session cost
cost_str = f"${data['cost']['total_cost_usd']:.3f}" if data['cost']['total_cost_usd'] >= 0.001 else f"{data['cost']['total_cost_usd']*100:.1f}¢"
# Build final status
status = f"\033[94m[{model}]\033[0m \033[93m{project_info}\033[0m{git_info}{test_status}{docker_status}{ci_status} \033[90m{cost_str}\033[0m"
print(status)
if __name__ == "__main__":
main()productivity-statusline.sh:
#!/bin/bash
# Productivity-focused status line with time tracking
input=$(cat)
# Basic info
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir' | xargs basename)
# Session metrics
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms')
LINES_ADDED=$(echo "$input" | jq -r '.cost.total_lines_added')
LINES_REMOVED=$(echo "$input" | jq -r '.cost.total_lines_removed')
COST=$(echo "$input" | jq -r '.cost.total_cost_usd')
# Format duration
if [ "$DURATION_MS" -gt 0 ]; then
DURATION_SEC=$((DURATION_MS / 1000))
if [ $DURATION_SEC -lt 60 ]; then
DURATION="${DURATION_SEC}s"
elif [ $DURATION_SEC -lt 3600 ]; then
DURATION="$((DURATION_SEC / 60))m"
else
DURATION="$((DURATION_SEC / 3600))h$((($DURATION_SEC % 3600) / 60))m"
fi
else
DURATION="0s"
fi
# Productivity metrics
NET_LINES=$((LINES_ADDED - LINES_REMOVED))
if [ $NET_LINES -gt 0 ]; then
PRODUCTIVITY_COLOR="\033[32m"
PRODUCTIVITY_SYMBOL="+"
elif [ $NET_LINES -lt 0 ]; then
PRODUCTIVITY_COLOR="\033[31m"
PRODUCTIVITY_SYMBOL=""
else
PRODUCTIVITY_COLOR="\033[33m"
PRODUCTIVITY_SYMBOL=""
fi
# Cost formatting
if (( $(echo "$COST < 0.01" | bc -l) )); then
COST_DISPLAY=$(echo "$COST * 100" | bc -l | cut -d. -f1)¢
else
COST_DISPLAY=\$$(printf "%.3f" "$COST")
fi
# Current time
CURRENT_TIME=$(date "+%H:%M")
# Build status line
echo -e "[$MODEL] 📁 $DIR | ⏰ $CURRENT_TIME | ⏱ $DURATION | ${PRODUCTIVITY_COLOR}📝 ${PRODUCTIVITY_SYMBOL}${NET_LINES}\033[0m | 💰 $COST_DISPLAY"#!/bin/bash
# Status line with caching for expensive operations
CACHE_DIR="$HOME/.claude/statusline_cache"
mkdir -p "$CACHE_DIR"
# Cache expensive git operations
get_git_info_cached() {
local cache_file="$CACHE_DIR/git_info_$(pwd | sed 's/\//_/g')"
local cache_timeout=5 # seconds
if [[ -f "$cache_file" && $(($(date +%s) - $(stat -f %m "$cache_file" 2>/dev/null || echo 0))) -lt $cache_timeout ]]; then
cat "$cache_file"
else
local git_info=""
if git rev-parse --git-dir > /dev/null 2>&1; then
local branch=$(git branch --show-current 2>/dev/null)
local status="✓"
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
status="±"
fi
git_info=" 🌿 $branch$status"
fi
echo "$git_info" > "$cache_file"
echo "$git_info"
fi
}
# Use cached functions in your status line
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir' | xargs basename)
GIT_INFO=$(get_git_info_cached)
echo "[$MODEL] 📁 $DIR$GIT_INFO"#!/usr/bin/env python3
# Status line with dynamic colors based on context
import json
import sys
import os
# Color schemes
COLORS = {
'production': {
'model': '\033[91m', # Red for production
'directory': '\033[93m',
'git': '\033[92m',
'reset': '\033[0m'
},
'staging': {
'model': '\033[93m', # Yellow for staging
'directory': '\033[94m',
'git': '\033[92m',
'reset': '\033[0m'
},
'development': {
'model': '\033[94m', # Blue for development
'directory': '\033[95m',
'git': '\033[92m',
'reset': '\033[0m'
}
}
def detect_environment():
"""Detect environment based on various indicators"""
# Check environment variables
env = os.environ.get('NODE_ENV', '').lower()
if env in ['production', 'prod']:
return 'production'
elif env in ['staging', 'stage']:
return 'staging'
# Check for environment files
if os.path.exists('.env.production'):
return 'production'
elif os.path.exists('.env.staging'):
return 'staging'
# Check git branch
try:
import subprocess
branch = subprocess.check_output(['git', 'branch', '--show-current'],
stderr=subprocess.DEVNULL).decode().strip()
if branch in ['main', 'master', 'production']:
return 'production'
elif branch in ['staging', 'stage']:
return 'staging'
except:
pass
return 'development'
def main():
data = json.load(sys.stdin)
# Detect environment and get colors
env = detect_environment()
colors = COLORS[env]
# Extract data
model = data['model']['display_name']
current_dir = os.path.basename(data['workspace']['current_dir'])
# Environment indicator
env_indicator = {
'production': '🔴 PROD',
'staging': '🟡 STAGE',
'development': '🟢 DEV'
}[env]
# Build colored status line
status = (f"{colors['model']}[{model}]{colors['reset']} "
f"{colors['directory']}📁 {current_dir}{colors['reset']} "
f"{env_indicator}")
print(status)
if __name__ == "__main__":
main()#!/bin/bash
# Multi-line status line (only first line is used, but you can prepare data)
input=$(cat)
# Collect comprehensive information
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir' | xargs basename)
COST=$(echo "$input" | jq -r '.cost.total_cost_usd')
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms')
LINES_NET=$(($(echo "$input" | jq -r '.cost.total_lines_added') - $(echo "$input" | jq -r '.cost.total_lines_removed')))
# Build a comprehensive but single-line status
# (Note: Only the first line of output is used by Claude Code)
STATUS_PARTS=()
STATUS_PARTS+=("[$MODEL]")
STATUS_PARTS+=("📁 $DIR")
# Add git info if available
if git rev-parse --git-dir > /dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
if [ -n "$BRANCH" ]; then
STATUS_PARTS+=("🌿 $BRANCH")
fi
fi
# Add metrics if significant
if [ $LINES_NET -ne 0 ]; then
STATUS_PARTS+=("📝 $LINES_NET lines")
fi
# Join with separators
IFS=' • '
echo "${STATUS_PARTS[*]}"
# You could output additional lines for your own debugging/logging
# (These won't be displayed in the status line)
echo "Debug: Session duration $(($DURATION_MS / 1000))s, Cost $COST" >&2#!/usr/bin/env python3
# Smart status line that shows different info based on context
import json
import sys
import os
import subprocess
from datetime import datetime
def get_priority_info(data):
"""Determine what information is most important to show"""
current_dir = data['workspace']['current_dir']
cost = data['cost']
priorities = []
# High priority: Expensive session
if cost['total_cost_usd'] > 0.05: # More than 5 cents
priorities.append(('cost', f"💰 ${cost['total_cost_usd']:.3f}", 1))
# High priority: Long session
if cost['total_duration_ms'] > 300000: # More than 5 minutes
duration_min = cost['total_duration_ms'] / 60000
priorities.append(('duration', f"⏱ {duration_min:.1f}m", 1))
# Medium priority: Significant changes
if cost['total_lines_added'] + cost['total_lines_removed'] > 50:
net_lines = cost['total_lines_added'] - cost['total_lines_removed']
sign = '+' if net_lines >= 0 else ''
priorities.append(('changes', f"📝 {sign}{net_lines}", 2))
# Medium priority: Git status with changes
try:
if os.path.exists('.git'):
status_output = subprocess.check_output(['git', 'status', '--porcelain'],
stderr=subprocess.DEVNULL).decode().strip()
if status_output:
change_count = len(status_output.split('\n'))
priorities.append(('git_changes', f"⚠️ {change_count} changed", 2))
except:
pass
# Low priority: Time
current_time = datetime.now().strftime("%H:%M")
priorities.append(('time', f"🕐 {current_time}", 3))
return priorities
def main():
data = json.load(sys.stdin)
# Basic info (always shown)
model = data['model']['display_name']
current_dir = os.path.basename(data['workspace']['current_dir'])
# Get prioritized additional info
priorities = get_priority_info(data)
# Sort by priority and take top items
priorities.sort(key=lambda x: x[2]) # Sort by priority level
# Build status line with space management
max_length = 80 # Estimate terminal width
base_status = f"[{model}] 📁 {current_dir}"
remaining_space = max_length - len(base_status)
additional_info = []
for info_type, info_text, priority in priorities:
if len(' • '.join(additional_info + [info_text])) < remaining_space - 3:
additional_info.append(info_text)
else:
break # Stop adding info if we're running out of space
# Combine everything
if additional_info:
full_status = f"{base_status} • {' • '.join(additional_info)}"
else:
full_status = base_status
print(full_status)
if __name__ == "__main__":
main()- Keep scripts fast: Status lines update frequently, so optimize for speed
- Cache expensive operations: Use caching for git status, file system checks
- Limit external command calls: Minimize subprocess execution
- Use efficient parsing: Prefer lightweight JSON parsing methods
- Be concise: Status lines should fit on one line
- Use meaningful colors: Color-code different types of information
- Choose clear icons: Use universally understood emojis and symbols
- Maintain consistency: Stick to a consistent visual style
#!/bin/bash
# Robust error handling example
input=$(cat)
# Always provide fallback values
MODEL=$(echo "$input" | jq -r '.model.display_name' 2>/dev/null || echo "Claude")
DIR=$(echo "$input" | jq -r '.workspace.current_dir' 2>/dev/null || pwd)
DIR_NAME=$(basename "$DIR" 2>/dev/null || echo "Unknown")
# Safe git operations
GIT_INFO=""
if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
BRANCH=$(git branch --show-current 2>/dev/null || echo "")
if [ -n "$BRANCH" ]; then
GIT_INFO=" | $BRANCH"
fi
fi
# Always output something, even if minimal
echo "[$MODEL] $DIR_NAME$GIT_INFO"Test your status line script:
# Create test JSON input
echo '{
"model": {"display_name": "Sonnet"},
"workspace": {"current_dir": "/test/project"},
"cost": {"total_cost_usd": 0.01, "total_duration_ms": 30000}
}' | ./your-statusline.shDebug with logging:
#!/usr/bin/env python3
import json
import sys
import logging
# Set up logging for debugging
logging.basicConfig(
filename='/tmp/statusline-debug.log',
level=logging.DEBUG,
format='%(asctime)s - %(message)s'
)
try:
data = json.load(sys.stdin)
logging.debug(f"Received data: {data}")
# Your status line logic
result = "Status line output"
logging.debug(f"Generated: {result}")
print(result)
except Exception as e:
logging.error(f"Error: {e}")
print("[Status Error]") # Fallback display#!/bin/bash
# Cross-platform compatible status line
# Detect OS for different commands
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
STAT_CMD="stat -f %m"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
STAT_CMD="stat -c %Y"
else
# Fallback
STAT_CMD="date +%s"
fi
# Use OS-appropriate commands
# Your status line logic here.claude/settings.json (Project):
{
"statusLine": {
"type": "command",
"command": "~/.claude/scripts/dev-statusline.py",
"padding": 0
}
}.claude/settings.json (Committed):
{
"statusLine": {
"type": "command",
"command": "./.claude/statusline/team-status.sh",
"padding": 1
}
}.claude/settings.local.json (Not committed):
{
"statusLine": {
"type": "command",
"command": "$HOME/.claude/statuslines/production-status.py",
"padding": 0
}
}-
Status line doesn't appear
- Check script is executable:
chmod +x your-script - Verify path in settings.json is correct
- Test script manually with mock JSON input
- Check script is executable:
-
Colors not displaying
- Ensure terminal supports ANSI colors
- Check color codes are properly formatted
- Test with simple color:
echo -e "\033[31mRed text\033[0m"
-
Script errors
- Add error logging to your script
- Test with minimal functionality first
- Check dependencies (jq, git, etc.) are installed
-
Performance issues
- Profile your script execution time
- Add caching for expensive operations
- Minimize external command calls
Test JSON Input:
{
"hook_event_name": "Status",
"session_id": "test-123",
"model": {"display_name": "Sonnet"},
"workspace": {
"current_dir": "/Users/you/project",
"project_dir": "/Users/you/project"
},
"cost": {
"total_cost_usd": 0.05,
"total_duration_ms": 120000,
"total_lines_added": 25,
"total_lines_removed": 8
}
}Test Command:
echo 'test-json-here' | ~/.claude/statusline.shPerformance Testing:
time echo 'test-json' | ~/.claude/statusline.sh# Minimal
[Sonnet] 📁 my-project
# Git-aware
[Sonnet] 📁 src • 🌿 feature-branch
# Development-focused
[Sonnet] ⚛️ React App • 🌿 main✓ • 🧪 • $0.02
# Performance-oriented
[Sonnet] 📁 api • ⚡ 250ms avg • 💰 $0.01
# Time-tracking
[Sonnet] 📦 Node.js • ⏰ 14:30 • ⏱ 25m • 📝 +42 lines
# Project-aware
[Sonnet] 🐍 data-pipeline@1.2.0 • 🌿 develop • 🐳 • 🔄Claude Code status lines provide a powerful way to customize your development interface with contextual information that matters to you. With status lines, you can:
- Monitor session metrics like cost, duration, and productivity
- Display project context including git status, environment, and dependencies
- Track development progress with real-time updates
- Customize the interface to match your workflow and preferences
- Enhance situational awareness during coding sessions
Start with simple status lines and gradually add more sophisticated features as you become comfortable with the system. The key is finding the right balance of information density and visual clarity for your workflow.
For more advanced customization and community examples, explore the Claude Code documentation and share your status line configurations with other developers.