The Problem
1M Context — But Not Really
When you run alternative models through Claude Code using shell aliases, the statusline JSON always reports context_window_size: 1000000. Claude Code doesn't know the downstream model has a smaller window. Your progress bar shows 8% when you're actually at 60%.
The danger
Autocompact fires at ~83% of the
reported window. But if your model's real limit is 200K and CC thinks you have 1M, you'll hit the model's context limit and get errors long before autocompact kicks in to save you.
What CC reports vs. reality
| Model | CC Reports | Actual Limit | At 120K tokens |
| Claude Opus 4.6 | 1M | 1M | 12% — correct |
| GLM-4.7 | 1M | 200K | 12% shown, actually 60% |
| GLM-4.7-Flash | 1M | 200K | 12% shown, actually 60% |
| Kimi K2.5 | 1M | 256K | 12% shown, actually 47% |
The Solution
Model-Aware Context Clamping
The statusline JSON includes model.id — the actual model name your alias configured. The script reads it, detects non-Anthropic models, clamps the context window to the model's real limit, and recalculates the percentage.
What the script shows
Model name + real context window. GLM gets 🤖, Kimi gets 🌙, Claude gets ◆. A ⚡ icon appears when clamping is active.
16-char bar with color coding. Green <50%, yellow 50-64%, orange 65-79% with ⚠️, red 80%+ with 🔥.
Red highlight for main/master, purple for dev, blue for feature branches and worktrees.
Shortened CWD so you always know where you are on disk.
Example output
🤖 GLM-4.7-Flash [200K] ⚡ │ █████████░░░░░░░ 60% feature/auth │ ~/G/StakTrakr
◆ Opus [1M] │ ██░░░░░░░░░░░░░░ 12% main │ ~/G/Devops
🌙 kimi-k2.5 [256K] ⚡ │ █████████████░░░ 85% 🔥 wt-fix-auth │ ~/G/.worktrees/fix-auth
Without clamping, those GLM and Kimi bars would both show ~12% — dangerously misleading.
The Script
statusline.sh
Save this as ~/.claude/statusline.sh and make it executable. The script reads JSON from stdin (piped by Claude Code), detects the model, and prints a formatted status bar to stdout.
#!/bin/bash
# Claude Code Statusline — GLM-aware context display
# Reads JSON from stdin, outputs ANSI-formatted status bar
input=$(cat)
# ── Model info ──
MODEL_ID=$(echo "$input" | jq -r '.model.id // "unknown"')
MODEL_NAME=$(echo "$input" | jq -r '.model.display_name // "?"')
# ── Context window ──
CTX_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
USED_PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
INPUT_TOKENS=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0')
# ── GLM context clamping ──
# CC reports 1M for all models but GLM caps at 200K (4.7/Flash)
ACTUAL_CTX=$CTX_SIZE
GLM_CLAMPED=""
if echo "$MODEL_ID" | grep -qi "GLM"; then
ACTUAL_CTX=200000
if [ "$CTX_SIZE" -gt 200000 ] 2>/dev/null; then
if [ "$INPUT_TOKENS" -gt 0 ] 2>/dev/null; then
USED_PCT=$(echo "scale=0; $INPUT_TOKENS * 100 / $ACTUAL_CTX" | bc 2>/dev/null || echo "$USED_PCT")
fi
GLM_CLAMPED=" ⚡"
fi
elif echo "$MODEL_ID" | grep -qi "kimi"; then
ACTUAL_CTX=256000
if [ "$CTX_SIZE" -gt 256000 ] 2>/dev/null; then
if [ "$INPUT_TOKENS" -gt 0 ] 2>/dev/null; then
USED_PCT=$(echo "scale=0; $INPUT_TOKENS * 100 / $ACTUAL_CTX" | bc 2>/dev/null || echo "$USED_PCT")
fi
GLM_CLAMPED=" ⚡"
fi
fi
# ── Format context size for display ──
if [ "$ACTUAL_CTX" -ge 1000000 ]; then
CTX_LABEL="1M"
elif [ "$ACTUAL_CTX" -ge 256000 ]; then
CTX_LABEL="256K"
elif [ "$ACTUAL_CTX" -ge 200000 ]; then
CTX_LABEL="200K"
else
CTX_LABEL="$((ACTUAL_CTX / 1000))K"
fi
# ── Git info ──
WORKTREE=$(echo "$input" | jq -r '.workspace.git_worktree // empty')
WT_BRANCH=$(echo "$input" | jq -r '.worktree.branch // empty')
BRANCH=""
if [ -n "$WT_BRANCH" ]; then
BRANCH="$WT_BRANCH"
elif [ -n "$WORKTREE" ]; then
BRANCH="$WORKTREE"
else
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
fi
# ── CWD (shorten your workspace prefix to taste) ──
CWD=$(echo "$input" | jq -r '.cwd // ""')
CWD_SHORT=$(echo "$CWD" | sed "s|$HOME|~|")
# ── Colors ──
RESET="\033[0m"; BOLD="\033[1m"; DIM="\033[2m"
BCYAN="\033[1;36m"; BGREEN="\033[1;32m"
BYELLOW="\033[1;33m"; BRED="\033[1;31m"
BORANGE="\033[1;38;5;208m"
BMAGENTA="\033[1;35m"; BBLUE="\033[1;34m"
BGRED="\033[41m"; WHITE="\033[37m"
# ── Progress bar ──
BAR_WIDTH=16
FILLED=$((USED_PCT * BAR_WIDTH / 100))
[ "$FILLED" -gt "$BAR_WIDTH" ] && FILLED=$BAR_WIDTH
EMPTY=$((BAR_WIDTH - FILLED))
if [ "$USED_PCT" -ge 80 ]; then
BAR_COLOR="$BRED"; PCT_COLOR="$BRED"; WARN=" 🔥"
elif [ "$USED_PCT" -ge 65 ]; then
BAR_COLOR="$BORANGE"; PCT_COLOR="$BORANGE"; WARN=" ⚠️"
elif [ "$USED_PCT" -ge 50 ]; then
BAR_COLOR="$BYELLOW"; PCT_COLOR="$BYELLOW"; WARN=""
else
BAR_COLOR="$BGREEN"; PCT_COLOR="$BGREEN"; WARN=""
fi
BAR="${BAR_COLOR}"
for ((i=0; i<FILLED; i++)); do BAR+="█"; done
BAR+="${DIM}"
for ((i=0; i<EMPTY; i++)); do BAR+="░"; done
BAR+="${RESET}"
# ── Branch display ──
BRANCH_STR=""
if [ -n "$BRANCH" ]; then
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
BRANCH_STR=" ${BGRED}${WHITE}${BOLD} ${BRANCH} ${RESET}"
elif echo "$BRANCH" | grep -q "^dev$"; then
BRANCH_STR=" ${BMAGENTA} ${BRANCH}${RESET}"
else
BRANCH_STR=" ${BBLUE} ${BRANCH}${RESET}"
fi
fi
# ── Model badge ──
if echo "$MODEL_ID" | grep -qi "GLM"; then
MODEL_BADGE="${BCYAN}🤖 ${MODEL_ID}${RESET}${DIM} [${CTX_LABEL}]${GLM_CLAMPED}${RESET}"
elif echo "$MODEL_ID" | grep -qi "kimi"; then
MODEL_BADGE="${BCYAN}🌙 ${MODEL_ID}${RESET}${DIM} [${CTX_LABEL}]${GLM_CLAMPED}${RESET}"
else
MODEL_BADGE="${BCYAN}◆ ${MODEL_NAME}${RESET}${DIM} [${CTX_LABEL}]${RESET}"
fi
# ── Output ──
echo -e "${MODEL_BADGE} ${DIM}│${RESET} ${BAR} ${PCT_COLOR}${BOLD}${USED_PCT}%${RESET}${WARN}${BRANCH_STR} ${DIM}│ ${CWD_SHORT}${RESET}"
Extending for other models
Add more
elif blocks for any model with a different context limit. Match on
MODEL_ID (the value your alias sets via
ANTHROPIC_DEFAULT_*_MODEL), set
ACTUAL_CTX to the real limit, and the rest handles itself.
Setup
Two Steps
Save the script, point your settings at it. Restart Claude Code to pick up the change.
1. Save and make executable
chmod +x ~/.claude/statusline.sh
2. Add to settings.json
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline.sh"
}
}
That's it. The statusline updates after each assistant message and on permission mode changes.
JSON fields available
Claude Code pipes a JSON object to your script's stdin on every update. Key fields this script uses:
| Field | Type | Description |
model.id | string | Model identifier — matches what your alias sets |
model.display_name | string | Friendly name (Opus, Sonnet, Haiku) |
context_window.context_window_size | number | Reported context limit (always 1M for extended models) |
context_window.used_percentage | number | Pre-calculated percentage (based on reported size) |
context_window.total_input_tokens | number | Actual tokens consumed — needed for recalculation |
workspace.git_worktree | string | Worktree name if in a linked worktree |
worktree.branch | string | Branch name in --worktree sessions |
cwd | string | Current working directory |
Dependency
The script requires
jq for JSON parsing and
bc for percentage calculation. Both are pre-installed on macOS. On Linux:
apt install jq bc.
How It Works
The Clamping Logic
Three steps: detect the model, override the context window, recalculate the percentage.
Step 1: Detect
Read model.id from the JSON. If it contains "GLM" (case-insensitive), the model is running through a GLM alias. Same check for "kimi".
Step 2: Clamp
Override context_window_size with the model's real limit. GLM-4.7 and Flash both cap at 200K. Kimi K2.5 caps at 256K. Native Claude models pass through unchanged.
Step 3: Recalculate
Divide total_input_tokens by the clamped context size. This gives the true percentage. The ⚡ icon appears when clamping is active, so you always know the displayed percentage is adjusted.
Why not just set CLAUDE_CODE_AUTO_COMPACT_WINDOW?
That env var controls
when autocompact fires, but it doesn't fix the statusline display. You still see a misleading percentage. The statusline script fixes what you
see. Use both together:
CLAUDE_CODE_AUTO_COMPACT_WINDOW="150000" in your alias to trigger compaction at the right time, and this script to display the right number.