Model Switch History Tracker - Statuslines
Claude Code model switch detector tracking transitions between Opus/Sonnet/Haiku with switch count, current model indicator, and cost impact visualization.
Open the source and read safety notes before installing.
Prerequisites
- Claude Code CLI installed and configured
- Bash shell available (bash 4.0+ recommended for case statements and string manipulation)
- jq command-line JSON processor (jq 1.6+ recommended for safe extraction with // defaults)
- date command with epoch timestamp support (date +%s for Unix timestamps)
- Terminal with ANSI color code support (256-color mode recommended for color-coded model tiers)
- Write access to home directory (~/.claude-code-model-history) for persistent model history files
Schema details
- Install type
- config
- Reading time
- 2 min
- Difficulty score
- 6
- Troubleshooting
- Yes
- Breaking changes
- No
- Script language
- bash
Script body
#!/usr/bin/env bash
# Model Switch History Tracker for Claude Code
# Tracks transitions between Claude models (Opus, Sonnet, Haiku)
# Read JSON from stdin
read -r input
# Extract current model info
current_model=$(echo "$input" | jq -r '.model.display_name // "unknown"')
session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
# Model history tracking directory
HISTORY_DIR="${HOME}/.claude-code-model-history"
mkdir -p "$HISTORY_DIR"
# Session-specific history file
HISTORY_FILE="${HISTORY_DIR}/${session_id}.history"
# Initialize history file if doesn't exist
if [ ! -f "$HISTORY_FILE" ]; then
echo "${current_model}" > "$HISTORY_FILE"
echo "0" >> "$HISTORY_FILE" # Switch count
fi
# Read previous model and switch count
previous_model=$(sed -n '1p' "$HISTORY_FILE")
switch_count=$(sed -n '2p' "$HISTORY_FILE")
# Detect model switch
if [ "$current_model" != "$previous_model" ] && [ "$previous_model" != "" ]; then
# Model changed - increment switch count
switch_count=$((switch_count + 1))
# Log switch event (append to history)
echo "$(date +%s)|${previous_model}→${current_model}" >> "$HISTORY_FILE"
fi
# Update current model and switch count (overwrite first 2 lines)
temp_file="${HISTORY_FILE}.tmp"
echo "${current_model}" > "$temp_file"
echo "${switch_count}" >> "$temp_file"
tail -n +3 "$HISTORY_FILE" >> "$temp_file" 2>/dev/null
mv "$temp_file" "$HISTORY_FILE"
# Determine model tier and color
case "$current_model" in
*"Opus"*|*"opus"*)
MODEL_COLOR="\033[38;5;201m" # Magenta: Opus (most expensive)
MODEL_ICON="💎"
MODEL_TIER="OPUS"
;;
*"Sonnet"*|*"sonnet"*)
MODEL_COLOR="\033[38;5;75m" # Blue: Sonnet (balanced)
MODEL_ICON="🎵"
MODEL_TIER="SONNET"
;;
*"Haiku"*|*"haiku"*)
MODEL_COLOR="\033[38;5;46m" # Green: Haiku (cheapest)
MODEL_ICON="🍃"
MODEL_TIER="HAIKU"
;;
*)
MODEL_COLOR="\033[38;5;250m" # Gray: Unknown
MODEL_ICON="❓"
MODEL_TIER="UNKNOWN"
;;
esac
# Switch frequency indicator
if [ $switch_count -eq 0 ]; then
SWITCH_STATUS="stable"
SWITCH_COLOR="\033[38;5;46m" # Green: No switches
elif [ $switch_count -le 3 ]; then
SWITCH_STATUS="${switch_count} switches"
SWITCH_COLOR="\033[38;5;226m" # Yellow: 1-3 switches
else
SWITCH_STATUS="${switch_count} switches!"
SWITCH_COLOR="\033[38;5;196m" # Red: 4+ switches (frequent switching)
fi
# Get last 3 switches for mini-history
last_switches=$(tail -n 3 "$HISTORY_FILE" | grep '→' | cut -d'|' -f2 | tr '\n' ' ' | sed 's/ $//')
if [ -n "$last_switches" ]; then
HISTORY_DISPLAY="| ${last_switches}"
else
HISTORY_DISPLAY=""
fi
RESET="\033[0m"
# Output statusline
echo -e "${MODEL_ICON} ${MODEL_COLOR}${MODEL_TIER}${RESET} | ${SWITCH_COLOR}${SWITCH_STATUS}${RESET} ${HISTORY_DISPLAY}"Full copyable content
{
"statusLine": {
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/statuslines/model-switch-history-tracker.sh",
"refreshInterval": 1000
}
}About this resource
Features
- Real-time model detection for Opus, Sonnet, and Haiku variants
- Switch count tracking showing total model changes in session
- Last 3 switches mini-history showing transition patterns (Opus→Sonnet→Haiku)
- Color-coded model tiers (magenta Opus, blue Sonnet, green Haiku)
- Switch frequency warnings (green stable, yellow 1-3, red 4+ switches)
- Persistent history storage per session in ~/.claude-code-model-history
- Cost awareness through model tier visualization
- Automatic switch event logging with timestamps
Use Cases
- Cost optimization by tracking Opus vs Sonnet vs Haiku usage patterns
- Identifying unnecessary model switching that inflates costs
- Understanding which tasks require which model tier
- Budget management through model tier awareness
- Debugging unexpected model changes in Claude Code settings
- Analyzing session patterns for optimal model selection strategy
Requirements
- Claude Code CLI installed and configured
- Bash shell available (bash 4.0+ recommended for case statements and string manipulation)
- jq command-line JSON processor (jq 1.6+ recommended for safe extraction with // defaults)
- date command with epoch timestamp support (date +%s for Unix timestamps)
- Terminal with ANSI color code support (256-color mode recommended for color-coded model tiers)
- Write access to home directory (~/.claude-code-model-history) for persistent model history files
Configuration
{
"statusLine": {
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/statuslines/model-switch-history-tracker.sh",
"refreshInterval": 1000
}
}
Examples
Enhanced Model Switch History Tracker with Cost Tracking
Extended version tracking cost impact of model switches
#!/usr/bin/env bash
# Enhanced Model Switch History Tracker with Cost Tracking
input=$(cat)
current_model=$(echo "$input" | jq -r '.model.display_name // .model.id // "unknown"')
session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
total_cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
HISTORY_DIR="${HOME}/.claude-code-model-history"
mkdir -p "$HISTORY_DIR"
HISTORY_FILE="${HISTORY_DIR}/${session_id}.history"
COST_FILE="${HISTORY_DIR}/${session_id}.cost"
if [ ! -f "$HISTORY_FILE" ]; then
echo "${current_model}" > "$HISTORY_FILE"
echo "0" >> "$HISTORY_FILE"
fi
previous_model=$(sed -n '1p' "$HISTORY_FILE" 2>/dev/null || echo "")
switch_count=$(sed -n '2p' "$HISTORY_FILE" 2>/dev/null || echo "0")
if [ "$current_model" != "$previous_model" ] && [ -n "$previous_model" ] && [ "$previous_model" != "unknown" ]; then
switch_count=$((switch_count + 1))
echo "$(date +%s)|${previous_model}→${current_model}" >> "$HISTORY_FILE"
# Track cost at switch point
echo "$(date +%s)|${total_cost}" >> "$COST_FILE"
fi
temp_file="${HISTORY_FILE}.tmp"
echo "${current_model}" > "$temp_file"
echo "${switch_count}" >> "$temp_file"
tail -n +3 "$HISTORY_FILE" >> "$temp_file" 2>/dev/null
mv "$temp_file" "$HISTORY_FILE"
case "$current_model" in
*"Opus"*|*"opus"*)
MODEL_COLOR="\033[38;5;201m"
MODEL_ICON="💎"
MODEL_TIER="OPUS"
;;
*"Sonnet"*|*"sonnet"*)
MODEL_COLOR="\033[38;5;75m"
MODEL_ICON="🎵"
MODEL_TIER="SONNET"
;;
*"Haiku"*|*"haiku"*)
MODEL_COLOR="\033[38;5;46m"
MODEL_ICON="🍃"
MODEL_TIER="HAIKU"
;;
*)
MODEL_COLOR="\033[38;5;250m"
MODEL_ICON="❓"
MODEL_TIER="UNKNOWN"
;;
esac
if [ $switch_count -eq 0 ]; then
SWITCH_STATUS="stable"
SWITCH_COLOR="\033[38;5;46m"
elif [ $switch_count -le 3 ]; then
SWITCH_STATUS="${switch_count} switches"
SWITCH_COLOR="\033[38;5;226m"
else
SWITCH_STATUS="${switch_count} switches!"
SWITCH_COLOR="\033[38;5;196m"
fi
last_switches=$(tail -n 3 "$HISTORY_FILE" 2>/dev/null | grep '→' | cut -d'|' -f2 | tr '\n' ' ' | sed 's/ $//')
if [ -n "$last_switches" ]; then
HISTORY_DISPLAY="| ${last_switches}"
else
HISTORY_DISPLAY=""
fi
# Format cost
cost_formatted=$(printf "$%.2f" $total_cost 2>/dev/null || echo "$${total_cost}")
RESET="\033[0m"
echo -e "${MODEL_ICON} ${MODEL_COLOR}${MODEL_TIER}${RESET} | ${SWITCH_COLOR}${SWITCH_STATUS}${RESET} | $${cost_formatted} ${HISTORY_DISPLAY}"
Model Switch History Tracker with Extended Model Detection
Version with enhanced model name matching for all Claude variants
#!/usr/bin/env bash
# Model Switch History Tracker with Extended Model Detection
input=$(cat)
current_model=$(echo "$input" | jq -r '.model.display_name // .model.id // "unknown"')
session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
HISTORY_DIR="${HOME}/.claude-code-model-history"
mkdir -p "$HISTORY_DIR"
HISTORY_FILE="${HISTORY_DIR}/${session_id}.history"
if [ ! -f "$HISTORY_FILE" ]; then
echo "${current_model}" > "$HISTORY_FILE"
echo "0" >> "$HISTORY_FILE"
fi
previous_model=$(sed -n '1p' "$HISTORY_FILE" 2>/dev/null || echo "")
switch_count=$(sed -n '2p' "$HISTORY_FILE" 2>/dev/null || echo "0")
if [ "$current_model" != "$previous_model" ] && [ -n "$previous_model" ] && [ "$previous_model" != "unknown" ]; then
switch_count=$((switch_count + 1))
echo "$(date +%s)|${previous_model}→${current_model}" >> "$HISTORY_FILE"
fi
temp_file="${HISTORY_FILE}.tmp"
echo "${current_model}" > "$temp_file"
echo "${switch_count}" >> "$temp_file"
tail -n +3 "$HISTORY_FILE" >> "$temp_file" 2>/dev/null
mv "$temp_file" "$HISTORY_FILE"
# Extended model detection (handles claude-3-5-sonnet, claude-opus-4-1, etc.)
case "$current_model" in
*"Opus"*|*"opus"*|*"claude-opus"*|*"claude-4"*)
MODEL_COLOR="\033[38;5;201m"
MODEL_ICON="💎"
MODEL_TIER="OPUS"
;;
*"Sonnet"*|*"sonnet"*|*"claude-3-5"*|*"claude-3-7"*)
MODEL_COLOR="\033[38;5;75m"
MODEL_ICON="🎵"
MODEL_TIER="SONNET"
;;
*"Haiku"*|*"haiku"*|*"claude-3-haiku"*)
MODEL_COLOR="\033[38;5;46m"
MODEL_ICON="🍃"
MODEL_TIER="HAIKU"
;;
*)
MODEL_COLOR="\033[38;5;250m"
MODEL_ICON="❓"
MODEL_TIER="UNKNOWN"
;;
esac
if [ $switch_count -eq 0 ]; then
SWITCH_STATUS="stable"
SWITCH_COLOR="\033[38;5;46m"
elif [ $switch_count -le 3 ]; then
SWITCH_STATUS="${switch_count} switches"
SWITCH_COLOR="\033[38;5;226m"
else
SWITCH_STATUS="${switch_count} switches!"
SWITCH_COLOR="\033[38;5;196m"
fi
last_switches=$(tail -n 3 "$HISTORY_FILE" 2>/dev/null | grep '→' | cut -d'|' -f2 | tr '\n' ' ' | sed 's/ $//')
if [ -n "$last_switches" ]; then
HISTORY_DISPLAY="| ${last_switches}"
else
HISTORY_DISPLAY=""
fi
RESET="\033[0m"
echo -e "${MODEL_ICON} ${MODEL_COLOR}${MODEL_TIER}${RESET} | ${SWITCH_COLOR}${SWITCH_STATUS}${RESET} ${HISTORY_DISPLAY}"
Model Switch History Tracker Installation Example
Complete setup script with history directory creation and Unicode character testing
#!/bin/bash
# Installation script for Model Switch History Tracker
# Check for jq (required for JSON parsing)
if ! command -v jq &> /dev/null; then
echo "Installing jq for JSON parsing..."
if [[ "$OSTYPE" == "darwin"* ]]; then
brew install jq
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
sudo apt-get install -y jq || sudo yum install -y jq
else
echo "Please install jq manually: https://stedolan.github.io/jq/"
fi
fi
# Create history directory
mkdir -p ~/.claude-code-model-history
echo "Model history directory created: ~/.claude-code-model-history"
# Test Unicode characters
if echo -e '💎 🎵 🍃 ❓ →' &> /dev/null; then
echo "Unicode characters supported"
else
echo "Warning: Unicode characters may not be supported in your terminal"
echo "Arrow character (→) may display as '->' or box"
fi
# Test date command (required for timestamps)
if date +%s &> /dev/null; then
echo "Date command working: $(date +%s)"
else
echo "Warning: date command may not support +%s format"
fi
mkdir -p .claude/statuslines
cat > .claude/statuslines/model-switch-history-tracker.sh << 'SCRIPT_EOF'
#!/usr/bin/env bash
# Model Switch History Tracker for Claude Code
# Tracks transitions between Claude models (Opus, Sonnet, Haiku)
read -r input
current_model=$(echo "$input" | jq -r '.model.display_name // .model.id // "unknown"')
session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
HISTORY_DIR="${HOME}/.claude-code-model-history"
mkdir -p "$HISTORY_DIR"
HISTORY_FILE="${HISTORY_DIR}/${session_id}.history"
if [ ! -f "$HISTORY_FILE" ]; then
echo "${current_model}" > "$HISTORY_FILE"
echo "0" >> "$HISTORY_FILE"
fi
previous_model=$(sed -n '1p' "$HISTORY_FILE" 2>/dev/null || echo "")
switch_count=$(sed -n '2p' "$HISTORY_FILE" 2>/dev/null || echo "0")
if [ "$current_model" != "$previous_model" ] && [ -n "$previous_model" ] && [ "$previous_model" != "unknown" ]; then
switch_count=$((switch_count + 1))
echo "$(date +%s)|${previous_model}→${current_model}" >> "$HISTORY_FILE"
fi
temp_file="${HISTORY_FILE}.tmp"
echo "${current_model}" > "$temp_file"
echo "${switch_count}" >> "$temp_file"
tail -n +3 "$HISTORY_FILE" >> "$temp_file" 2>/dev/null
mv "$temp_file" "$HISTORY_FILE"
case "$current_model" in
*"Opus"*|*"opus"*)
MODEL_COLOR="\033[38;5;201m"
MODEL_ICON="💎"
MODEL_TIER="OPUS"
;;
*"Sonnet"*|*"sonnet"*)
MODEL_COLOR="\033[38;5;75m"
MODEL_ICON="🎵"
MODEL_TIER="SONNET"
;;
*"Haiku"*|*"haiku"*)
MODEL_COLOR="\033[38;5;46m"
MODEL_ICON="🍃"
MODEL_TIER="HAIKU"
;;
*)
MODEL_COLOR="\033[38;5;250m"
MODEL_ICON="❓"
MODEL_TIER="UNKNOWN"
;;
esac
if [ $switch_count -eq 0 ]; then
SWITCH_STATUS="stable"
SWITCH_COLOR="\033[38;5;46m"
elif [ $switch_count -le 3 ]; then
SWITCH_STATUS="${switch_count} switches"
SWITCH_COLOR="\033[38;5;226m"
else
SWITCH_STATUS="${switch_count} switches!"
SWITCH_COLOR="\033[38;5;196m"
fi
last_switches=$(tail -n 3 "$HISTORY_FILE" 2>/dev/null | grep '→' | cut -d'|' -f2 | tr '\n' ' ' | sed 's/ $//')
if [ -n "$last_switches" ]; then
HISTORY_DISPLAY="| ${last_switches}"
else
HISTORY_DISPLAY=""
fi
RESET="\033[0m"
echo -e "${MODEL_ICON} ${MODEL_COLOR}${MODEL_TIER}${RESET} | ${SWITCH_COLOR}${SWITCH_STATUS}${RESET} ${HISTORY_DISPLAY}"
SCRIPT_EOF
chmod +x .claude/statuslines/model-switch-history-tracker.sh
# Add to settings.json
if [ ! -f .claude/settings.json ]; then
echo '{"statusLine":{"type":"command","command":"$CLAUDE_PROJECT_DIR/.claude/statuslines/model-switch-history-tracker.sh","refreshInterval":1000}}' > .claude/settings.json
else
jq '.statusLine = {"type":"command","command":"$CLAUDE_PROJECT_DIR/.claude/statuslines/model-switch-history-tracker.sh","refreshInterval":1000}' .claude/settings.json > .claude/settings.json.tmp
mv .claude/settings.json.tmp .claude/settings.json
fi
echo "Model Switch History Tracker installed successfully!"
echo "Note: History files stored in ~/.claude-code-model-history/"
echo "View history: cat ~/.claude-code-model-history/[session-id].history"
Troubleshooting
Model always showing UNKNOWN despite valid Claude model running
Check model.display_name field: echo '$input' | jq .model.display_name. Script matches case-insensitively on 'Opus', 'Sonnet', 'Haiku' keywords. If model name doesn't contain these (e.g., 'claude-3-5-sonnet-20241022'), add custom matching: 'claude-3-5-sonnet') MODEL_TIER='SONNET'. Verify field exists and has expected format. Check model.id as fallback: echo '$input' | jq .model.id.
Switch count not incrementing when changing models
Verify session_id is consistent: echo '$input' | jq .session_id. Each session has separate history file. If session_id changes, switch count resets (expected). Check history file exists: ls ~/.claude-code-model-history/${session_id}.history. Verify file format: first line = current model, second line = switch count. Manually test: echo 'Opus' > file.history && echo '0' >> file.history. Check model comparison logic: ensure previous_model is not empty.
Permission denied when creating model history directory
Ensure HOME environment variable is set: echo $HOME. Check write permissions: mkdir -p ~/.claude-code-model-history. If permission denied, change location: HISTORY_DIR='/tmp/claude-model-history-$(whoami)'. Verify statusline script runs with correct user permissions. Check disk space: df -h ~. Verify directory creation: mkdir -p ~/.claude-code-model-history && touch ~/.claude-code-model-history/test && rm ~/.claude-code-model-history/test.
Last 3 switches history not displaying transitions
History format is 'timestamp|ModelA→ModelB' (e.g., '1730000000|Opus→Sonnet'). Check file content: tail ~/.claude-code-model-history/${session_id}.history. Grep filter looks for '→' character - ensure terminal encoding supports Unicode arrow. Alternative: replace → with ASCII '->' in script. Verify grep works: echo 'test→test' | grep '→'. Check cut command: echo '123|Opus→Sonnet' | cut -d'|' -f2 (should return 'Opus→Sonnet').
Switch frequency showing incorrect count
Switch count stored on line 2 of history file. Verify: sed -n '2p' ~/.claude-code-model-history/${session_id}.history. Count only increments when current_model != previous_model AND previous_model is not empty. New sessions start at 0 (expected). Manually reset: echo '0' to line 2 if corrupted. Check arithmetic: switch_count=$((switch_count + 1)). Verify sed reading: sed -n '2p' file.history (should return number).
History file corruption or invalid format
Verify file format: first line = current model, second line = switch count, subsequent lines = switch events. Check file exists and is readable: ls -l ~/.claude-code-model-history/${session_id}.history. Verify sed commands work: sed -n '1p' file (first line), sed -n '2p' file (second line). Check temp file operations: ensure temp_file is created and moved atomically. If corrupted, delete history file to reset: rm ~/.claude-code-model-history/${session_id}.history.
Unicode arrow character (→) not displaying correctly
Verify terminal encoding supports Unicode: locale charmap (should be UTF-8). Set terminal to UTF-8: export LANG=en_US.UTF-8. Test arrow: echo -e '→'. If not supported, modify script to use ASCII '->' instead. Check grep for arrow: echo 'test→test' | grep '→' (should match). Verify tr and sed handle Unicode: echo '→' | tr '→' '->'.
Model switch detection not working for new model variants
Script uses case-insensitive pattern matching on 'Opus', 'Sonnet', 'Haiku'. For new variants (e.g., 'claude-3-5-sonnet-20241022'), extend case statement: 'claude-3-5') MODEL_TIER='SONNET'. Check model name format: echo '$input' | jq .model.display_name. Add custom patterns for specific model IDs. Verify case statement syntax: case "$current_model" in pattern) commands ;; esac.
- Features
- Use Cases
- Requirements
- Configuration
- Examples
- Enhanced Model Switch History Tracker with Cost Tracking
- Model Switch History Tracker with Extended Model Detection
- Model Switch History Tracker Installation Example
- Troubleshooting
- Model always showing UNKNOWN despite valid Claude model running
- Switch count not incrementing when changing models
- Permission denied when creating model history directory
- Last 3 switches history not displaying transitions
- Switch frequency showing incorrect count
- History file corruption or invalid format
- Unicode arrow character (→) not displaying correctly
Source citations
Signals
Loading live community signals…
A short, calm digest of reviewed Claude resources. Unsubscribe any time.