Statusline anpassen
- Den Vertrag verstehen: Claude Code leitet die gesamte Sitzung als JSON an ein Skript und gibt aus, was das Skript ausgibt
- Es auf beiden Wegen einrichten — /statusline in natürlicher Sprache fragen oder den statusLine-Block selbst schreiben
- Die Handvoll Felder auswählen, die ihren Platz wirklich verdienen, aus den rund sechzig verfügbaren
- Die acht Ereignisse kennen, die dein Skript erneut ausführen, das 300-ms-Debounce und wann du refreshInterval brauchst
- Drei Rezepte bauen: einen Kontextbalken, eine PR-und-Rate-Limit-Zeile und einen Prompt-Cache-Gesundheitsindikator
- Die vier Fallstricke vermeiden: tput cols funktioniert nicht, Null-Felder vor dem ersten API-Aufruf, Abbruch laufender Ausführungen und der Verlust der Tastaturhinweise
Die Statusline ist ein Streifen am unteren Rand von Claude Code, der jedes Shell-Skript ausführt, das du konfigurierst. Claude Code leitet den gesamten Sitzungszustand als JSON auf stdin an es weiter; dein Skript gibt etwas aus; Claude Code zeigt das an. Es läuft lokal und verbraucht keine API-Tokens.
Dieser Vertrag ist die ganze Funktion. Alles Folgende handelt davon, was im JSON steht und wann das Skript läuft.
Sie wird in einer eigenen Zeile über den eingebauten Fußzeilen-Badges dargestellt, statt sie zu ersetzen — doch wenn du eine konfigurierst, zeigt Claude Code die meisten Tastaturhinweise der Fußzeile nicht mehr an, darunter esc to interrupt, den Fallback ? for shortcuts und den Diktierhinweis hold space to speak. Gut zu wissen, bevor du dieses Setup an ein Teammitglied weitergibst, das sich auf diese Hinweise verlässt.
Einrichten
Zwei Wege. Der schnelle ist, zu beschreiben, was du willst:
Claude Code das Skript für dich schreiben lassen
/statusline show model name and context percentage with a progress bar
Es erzeugt ein Skript in ~/.claude/, aktualisiert deine Einstellungen und fragt für die Dateiänderungen nach Erlaubnis. Mit /statusline delete (oder clear, oder „remove it“) nimmst du sie wieder heraus.
Der manuelle Weg ist ein statusLine-Block in ~/.claude/settings.json oder den Projekteinstellungen:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2,
"refreshInterval": 10
}
}
Vier Felder, drei davon optional:
| Feld | Bedeutung |
|---|---|
type | "command" |
command | Ein Skriptpfad oder ein Inline-Shell-Befehl — er läuft in einer Shell, ein Einzeiler funktioniert also |
padding | Zusätzlicher horizontaler Abstand in Zeichen, Standard 0. Relative Einrückung, kein absoluter Abstand zum Terminalrand |
refreshInterval | Alle N Sekunden erneut ausführen zusätzlich zu den Ereignisauslösern. Minimum 1. Ungesetzt lassen, um nur bei Ereignissen zu laufen |
hideVimModeIndicator | Auf true setzen, wenn dein Skript vim.mode selbst darstellt, damit der Modus nicht doppelt angezeigt wird |
Weil command in einer Shell läuft, braucht die kleinstmögliche nützliche Statusline gar keine Skriptdatei — nur jq:
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}
Was im JSON steht
Die Eingabe ist groß — Modell, Workspace, Kosten, Kontextfenster, Rate-Limits, PR-Status, Worktree, Vim-Modus, Prompt-Cache. Die Versuchung ist, alles anzuzeigen. Widerstehe ihr: Eine Statusline, die du nicht mehr liest, ist schlechter als keine.
Das sind die Felder, die für die meisten Leute ihren Platz verdienen, gruppiert nach der Überraschung, die sie verhindern:
| Feld | Verhindert |
|---|---|
model.display_name | Opus für eine triviale Aufgabe zu verbrennen |
context_window.used_percentage | Mitten in der Aufgabe ohne Kontext dazustehen — dein Signal für /compact |
workspace.current_dir, workspace.git_worktree | Den falschen Checkout zu bearbeiten, wenn mehrere Worktrees offen sind |
rate_limits.five_hour.used_percentage | Ohne Warnung gegen eine Wand zu laufen |
cost.total_cost_usd | Dass eine Sitzung unbemerkt teuer wird (clientseitige Schätzung zum Listenpreis — nicht deine Rechnung) |
effort.level, fast_mode | Zu vergessen, dass du den Effort auf max gelassen hast |
pr.number, pr.review_state | In einen Browser zu wechseln, um zu prüfen, ob das Review eingetroffen ist |
prompt_cache.hit_ratio, prompt_cache.warm | Das leiseste Kostenleck überhaupt — siehe das dritte Rezept |
Drei strukturelle Anmerkungen zum Schema, die echte Debugging-Zeit sparen:
cwdundworkspace.current_direnthalten denselben Wert. Bevorzugeworkspace.current_dir, der Konsistenz mitworkspace.project_dirwegen — das ist der Ort, an dem Claude Code gestartet wurde, und er kann abweichen, sobald sich das Arbeitsverzeichnis mitten in der Sitzung ändert.workspace.git_worktreeundworktree.*sind nicht dasselbe.workspace.git_worktreeist für jeden verknüpften Worktree gefüllt, der mitgit worktree adderstellt wurde, und fehlt im Hauptbaum. Das Objektworktree.*existiert nur, solange die Sitzung in einer Claude-Code-Worktree-Sitzung ist.used_percentageist nur Eingabe. Es wird ausinput_tokens + cache_creation_input_tokens + cache_read_input_tokensberechnet und schließt Ausgabe-Tokens aus. Wenn du selbst einen Prozentwert auscurrent_usageberechnest, verwende dieselbe reine Eingabe-Formel, sonst stimmt deine Zahl nicht mit der von Claude Code überein.
Wann dein Skript läuft
Einmal beim Sitzungsstart, auch beim Fortsetzen. Danach bei jedem dieser Ereignisse:
- Der häufige Fall, und der Grund, warum ein Kontext-Prozentwert ganz ohne Timer ungefähr aktuell bleibt.
- /compact wird fertig, der Berechtigungsmodus wechselt, der Vim-Modus wird umgeschaltet oder du bearbeitest den Befehl in deinen statusLine-Einstellungen.
- Ein Rate-Limit-Fenster erreicht sein resets_at oder ein warmer Prompt-Cache erreicht sein expires_at. Claude Code liest diese Zeitstempel aus dem JSON, das es dir schon gegeben hat, und plant einen erneuten Lauf.
- Der einzige Auslöser, der feuert, während nichts passiert — und genau dann brauchst du ihn.
Aus dieser Liste folgen drei Verhaltensweisen:
- Updates werden mit 300 ms entprellt, sodass schnelle Änderungen zu einem Lauf gebündelt werden. Das Bearbeiten des
commandselbst überspringt das Debounce und läuft sofort. - Ein neuer Auslöser bricht ein laufendes Skript ab. Halte es schnell. Eine Statusline, die einen langsamen Netzwerkaufruf per Shell startet, wird regelmäßig beendet, bevor sie etwas ausgibt.
- Die Ereignisauslöser werden still, wenn die Hauptsitzung im Leerlauf ist — der klassische Fall ist ein Koordinator, der auf Subagents im Hintergrund wartet, die unter dir den Git-Zustand verändern. Das, plus eine Uhr oder jedes extern bezogene Segment, ist wofür
refreshIntervalda ist.
Dein Skript kann mehr als eine einfache Zeile ausgeben: jedes echo ist eine Zeile, ANSI-Escape-Codes geben dir Farbe (\033[32m grün, \033[0m zurücksetzen), und OSC-8-Sequenzen machen Text in iTerm2, Kitty oder WezTerm anklickbar.
Drei Rezepte
Rezept 1: Kontextbalken
Die wertvollste Zeile, und die, die du haben solltest, selbst wenn du sonst nichts hinzufügst. Beachte die // 0-Fallbacks — current_usage ist vor dem ersten API-Aufruf und erneut direkt nach /compact null, sodass ein ungeschützter jq-Ausdruck genau in den Momenten null ausgibt, in denen du am wahrscheinlichsten hinsiehst.
#!/usr/bin/env bash
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name // "claude"')
dir=$(echo "$input" | jq -r '.workspace.current_dir // "."')
pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
# 20-cell bar: fill proportional to pct
filled=$(( pct / 5 ))
bar=""
for ((i = 0; i < 20; i++)); do
if (( i < filled )); then bar+="▓"; else bar+="░"; fi
done
# green under 60%, yellow under 85%, red above
if (( pct < 60 )); then color="\033[32m"
elif (( pct < 85 )); then color="\033[33m"
else color="\033[31m"
fi
printf "%s · %s · ${color}%s %s%%\033[0m" "$model" "$(basename "$dir")" "$bar" "$pct"
Rezept 2: Branch, PR-Status und dein Rate-Limit-Spielraum
pr.* spiegelt das PR-Badge der Fußzeile — und in einem GitLab-Repository wird es stattdessen aus dem offenen Merge Request des Branches gefüllt, wobei pr.kind auf mr steht und pr.number die MR-Nummer ist. Die Rate-Limit-Felder sind die, die sich Leute im Nachhinein gewünscht hätten.
#!/usr/bin/env bash
input=$(cat)
branch=$(git branch --show-current 2>/dev/null)
wt=$(echo "$input" | jq -r '.workspace.git_worktree // empty')
pr=$(echo "$input" | jq -r '.pr.number // empty')
state=$(echo "$input" | jq -r '.pr.review_state // empty')
limit=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
line="${branch:-no-git}"
[ -n "$wt" ] && line+=" ⎇ $wt"
[ -n "$pr" ] && line+=" · #$pr${state:+ ($state)}"
[ -n "$limit" ] && line+=" · 5h ${limit%.*}%"
printf "%s" "$line"
Rezept 3: Cache-Gesundheit, das Leck, das du nicht siehst
Das ist das Rezept, das niemand schreibt, und das mit dem höchsten Nutzen in langen agentischen Sitzungen. Prompt Caching macht eine Sitzung mit 100 Zügen bezahlbar — und ein einziges sich änderndes Byte oben in deinem Prompt zerstört es stillschweigend, sodass das gesamte Präfix bei jedem Zug zum vollen Preis neu verarbeitet wird. Nichts in der Standardoberfläche sagt dir, dass das passiert.
prompt_cache tut es. Zwei Felder tragen den Großteil des Signals — warm (liegt das Präfix noch innerhalb seiner TTL) und hit_ratio (Cache-Lesevorgänge im Verhältnis zu allen Eingabe-Tokens, 0–1) — und last_miss_cause.causes nennt den Schuldigen: tools_changed, system_prompt_changed, ttl_expired_5m oder likely_server_side.
#!/usr/bin/env bash
input=$(cat)
warm=$(echo "$input" | jq -r '.prompt_cache.warm // empty')
ratio=$(echo "$input" | jq -r '.prompt_cache.hit_ratio // empty')
cause=$(echo "$input" | jq -r '.prompt_cache.last_miss_cause.causes[0] // empty')
misses=$(echo "$input" | jq -r '.prompt_cache.misses // 0')
# prompt_cache is absent until the main conversation's first API response
[ -z "$warm" ] && { printf "cache: —"; exit 0; }
pct=$(awk "BEGIN{printf \"%d\", ${ratio:-0} * 100}")
if [ "$warm" = "true" ]; then icon="\033[32m●\033[0m"; else icon="\033[31m○\033[0m"; fi
printf "cache: ${icon} %s%% hit" "$pct"
(( misses > 0 )) && printf " · %s miss%s%s" "$misses" "$([ "$misses" -eq 1 ] || echo es)" "${cause:+ ($cause)}"
Wenn system_prompt_changed als Ursache bei jedem Zug erscheint, ist etwas in deinem Prompt-Präfix nicht deterministisch — ein Zeitstempel in einer CLAUDE.md, ein Hook, der wechselnde Ausgabe einschleust. tools_changed bei jedem Zug bedeutet meist, dass ein MCP-Server Tools uneinheitlich registriert. Beides lässt sich beheben, sobald du es sehen kannst. Claude Code zeigt dieselben Statistiken in der Zeile Prompt cache (main) von /usage, wenn du lieber bei Bedarf nachsiehst.
Beachte, dass diese Statistiken nur die Hauptunterhaltung abdecken — Subagent-Anfragen werden nicht mitgezählt.
Vier Fallstricke
tput colsfunktioniert nicht. Claude Code fängt die Ausgabe deines Skripts ab, statt sie mit dem Terminal zu verbinden, sodasstput colsund sprachinterne Breitenerkennung die Terminalgröße nicht sehen können. Lies stattdessen die UmgebungsvariablenCOLUMNSundLINES— Claude Code setzt beide vor jedem Lauf auf die aktuellen Maße.- Schütze jedes Feld.
current_usageist vor dem ersten API-Aufruf und erneut direkt nach/compactnull;prompt_cacheundrate_limits.spend_limitfehlen bis zur ersten API-Antwort;pr.review_statekann fehlen, selbst wennprvorhanden ist;effort.levelfehlt bei Modellen ohne den Parameter. Verwende// emptyoder// 0bei allem. - Langsame Skripte werden abgebrochen, nicht eingereiht. Ein neuer Auslöser beendet den laufenden Lauf. Wenn du externe Daten willst, lege sie aus einem separaten Prozess in einer Datei ab und lass die Statusline die Datei lesen.
- Die Dollarbeträge der Ausgabenlimits hinken hinterher. Hinter einem Claude-Apps-Gateway kommt
used_percentagemit jeder Antwort, aberused_usd,limit_usdundperiodwerden separat etwa alle fünf Minuten abgerufen — sie können dem Prozentwert also kurzzeitig widersprechen. Behandle sie als optional, selbst wennspend_limitvorhanden ist.
Keine Lust auf Skripten?
Community-Tools wie ccstatusline liefern fertige, konfigurierbare Statuslines, die du einfach einsetzen kannst. Sie sind ein vernünftiger Ausgangspunkt — und wie jedes Drittanbieter-Skript, das bei jeder Assistant-Nachricht mit deinen Rechten läuft, solltest du sie lesen, bevor du ihnen vertraust. Siehe Drittanbieter-Code prüfen.
Prüfe dein Verständnis
0/5- Die Statusline ist nur ein Skript: Claude Code leitet die gesamte Sitzung als JSON auf stdin und stellt dar, was das Skript auf stdout ausgibt. Sie läuft lokal und kostet keine Tokens.
- /statusline mit einer Beschreibung in natürlicher Sprache schreibt das Skript und verdrahtet die Einstellungen für dich. Die manuelle Form ist ein statusLine-Block mit type, command und den optionalen padding, refreshInterval und hideVimModeIndicator.
- Von den rund sechzig Feldern verdienen eine Handvoll ihren Platz: model.display_name, context_window.used_percentage, das Verzeichnis oder der Worktree, rate_limits.five_hour.used_percentage und — in langen Sitzungen — prompt_cache.
- Ereignisgesteuert mit 300 ms Debounce. Eine Hauptsitzung im Leerlauf löst keine Ereignisse aus, daher brauchen zeitbasierte oder extern bezogene Segmente refreshInterval. Ein neuer Auslöser BRICHT ein laufendes Skript AB, also halte es schnell.
- Schütze jedes Feld mit // empty oder // 0: current_usage ist vor dem ersten API-Aufruf und direkt nach /compact null, und prompt_cache und spend_limit fehlen bis zur ersten API-Antwort.
- used_percentage ist nur Eingabe (frische Eingabe + Cache-Schreibvorgänge + Cache-Lesevorgänge). Übernimm diese Formel, wenn du es selbst berechnest.
- tput cols kann das Terminal aus dem Skript heraus nicht sehen — lies COLUMNS und LINES, die Claude Code vor jedem Lauf setzt.
- Ein prompt_cache-Segment ist die wirkungsvollste ungewöhnliche Ergänzung: warm plus hit_ratio verraten dir, ob dein Präfix gecacht wird, und last_miss_cause.causes benennt, was es gebrochen hat.
Weiter
- Kontextverwaltung — was der Prozentwert, den du gerade auf den Bildschirm gebracht hast, tatsächlich bedeutet und wie du darauf reagierst
- Prompt Caching — warum sich ein Blick auf
hit_ratiolohnt und was ein gecachtes Präfix zerstört - settings.json — wo der
statusLine-Block liegt und welche Ebene gewinnt - Git-Worktrees — die Funktion, die
workspace.git_worktreeundworktree.*beschreiben