Skip to main content
statuslinesSource-backedReview first Safety · Privacy ·

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.

by JSONbored·added 2025-10-25·
Claude Code
HarnessClaude Code
Language:bash
Review first review before installing

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
Runtime and command metadata
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.

#model-switching#opus-sonnet-haiku#cost-optimization#model-tracking#switch-history

Source citations

Signals

Loading live community signals…

More like this, weekly

A short, calm digest of reviewed Claude resources. Unsubscribe any time.