Die Statuszeile in Claude Code ist ein Skript. Schreib sie nicht fest.
Statuszeile in Claude Code einrichten: JSON auf stdin, settings.json und die Felder, die zählen. Ein Baukasten kopiert einen Prompt, kein festgeschriebenes Skript.
Manuel Hedinger
Ich kompaktiere zu spät. Das Fünf-Stunden-Limit merke ich, wenn die nächste Antwort eine Bezahlschranke ist. Welches Modell ich vor drei Messages umgestellt habe, habe ich schon wieder vergessen. All das steckt bereits in der Session. Claude Code legt es nur nicht dorthin, wo ich hinschaue.
Genau dafür ist die Statuszeile da.
Die kurze Antwort
Die Statuszeile ist eine Zeile über den eingebauten Fusszeilen-Badges. Claude
Code führt einen Befehl aus, schickt ihm die aktuelle Session als JSON auf
stdin und zeichnet, was der Befehl auf stdout schreibt. Sie verbraucht keine
API-Tokens. Sie ersetzt die Badges nicht. Sie blendet aber die meisten
Tastaturhinweise aus — esc to interrupt, ? for shortcuts, den
Hold-Space-to-Speak-Hinweis. Wenn du davon lebst, ist das der Preis.
Drei Wege führen hin. Beschreib die Zeile mit /statusline und lass Claude
das Skript nach ~/.claude/ schreiben. Setz selbst ein statusLine-Objekt in
~/.claude/settings.json. Oder stell das Layout im
Statusleisten-Baukasten zusammen und füg den
Prompt ein. Der Baukasten gibt kein Skript aus. Ein Skript in einem Blogpost
ist veraltet, sobald Anthropic ein Feld dazunimmt. Einen Prompt liest Claude
Code gegen die Docs, die es schon hat.
Die offizielle Anleitung ist Customize your status line. Was folgt, ist die Fassung, die ich selbst gern gehabt hätte: was sich zu zeigen lohnt, was man weglässt — und die Stellen, die lautlos scheitern.

/statusline fragen
Zum Beispiel:
/statusline show model name and context percentage with a progress barClaude legt ein Skript unter ~/.claude/ an und schreibt die Settings. Die
Datei-Edit-Prompts musst du bestätigen, wenn es danach fragt. Das ist der
richtige erste Schritt, wenn du noch nie eine Statuszeile hattest und dir
egal ist, wie sie innen aussieht.
Zum Entfernen reicht /statusline delete (oder clear, oder remove it).
Oder du löschst das Feld statusLine selbst aus der settings.json.
Die Settings schreiben
User-Settings liegen in ~/.claude/settings.json. Projekt-Settings gehen
auch. Die Form ist klein:
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 2
}
}type ist "command". command ist ein Skriptpfad oder ein Shell-Schnipsel.
Settings laden beim Speichern nach; Claude Code musst du nicht neu starten.
padding ist extra horizontaler Abstand in Zeichen, zusätzlich zur
eingebauten Rinne. Standard 0.
refreshInterval führt den Befehl alle N Sekunden zusätzlich zu den
ereignisgetriebenen Updates aus. Minimum 1. Setz es für eine Uhr, oder wenn
Hintergrund-Subagents den Git-Stand ändern, während die Hauptsession idle
ist. Lass es weg, wenn dich nur Antworten interessieren.
hideVimModeIndicator blendet das eingebaute -- INSERT -- unter dem Prompt
aus. Setz es, wenn dein Skript vim.mode schon selbst druckt, sonst siehst du
den Modus doppelt.
Der Befehl läuft in einer Shell, deshalb reicht für den ersten Check eine Zeile:
{
"statusLine": {
"type": "command",
"command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
}
}Du brauchst jq. Unter Windows den Pfad mit Schrägstrichen
schreiben (C:/Users/you/.claude/statusline.sh). Git Bash frisst Backslashes
als Escape-Zeichen — und der Befehl scheitert ohne eine Zeile auf dem Schirm.
Zusammensetzen, dann Claude schreiben lassen
Ich hefte kein Gist von statusline.sh ab. Das stdin-JSON wächst. Das Skript
von letztem Jahr kennt rate_limits nicht, auch nicht pr.kind für GitLab,
auch nicht COLUMNS. Der Follow-up, den ich wirklich will, heisst «now make
the bar wider» oder «add the open PR».
Deshalb kopiert der Statusleisten-Baukasten einen Prompt, keine Datei. Du ziehst die Segmente an ihren Platz — Verzeichnis, Branch, Modell, Kontext, das 5-Stunden- und das 7-Tage-Limit, Kosten, eine Uhr, geänderte Zeilen, die PR, der Worktree — wählst eine Palette und fügst den englischen Prompt in eine Session ein. Claude schreibt das Skript, zeigt in der settings.json darauf und bleibt für die nächste Änderung da.
Es läuft im Browser. Was du anordnest, wird nicht hochgeladen.
Wie die Daten laufen
Claude Code serialisiert die laufende Session und piped sie rein. Dein Befehl druckt eine Zeile. Diese Zeile ist die Row.

Der Befehl läuft einmal, wenn eine Session startet, auch beim Fortsetzen. Danach wieder, wenn:
- eine neue Assistant-Message ankommt
/compactfertig ist- sich der Permission-Modus ändert
- der Vim-Modus umschaltet
- du
commandin den Settings änderst - ein
refreshInterval-Timer zündet, falls du einen gesetzt hast
Updates werden mit 300ms entprellt. Eine Änderung an command selbst
überspringt das und läuft sofort. Kommt ein neues Update, während das Skript
noch läuft, wird der laufende Lauf abgebrochen. Editierst du das Skript auf
der Platte, greift der nächste Trigger; extra nachladen musst du nicht.
Die ereignisgetriebenen Updates werden still, wenn die Hauptsession idle ist,
zum Beispiel während ein Coordinator auf Hintergrund-Subagents wartet. Dafür
ist refreshInterval da.
Du kannst mehrere Zeilen drucken. Du kannst ANSI-Farben nutzen. Du kannst Text
in OSC-8-Sequenzen packen, damit er klickbar wird (Cmd-Klick auf macOS,
Ctrl-Klick sonst) in iTerm2, Kitty oder WezTerm. Terminal.app kann das nicht.
Stehen die Sequenzen als literales \e]8;; da, nimm printf '%b', nicht
echo -e.
Ruf innerhalb des Skripts nicht tput cols auf. Claude Code fängt stdout ab,
statt dich an das Terminal zu hängen — die Breitenerkennung von innen ist
blind. Lies COLUMNS und LINES. Die setzt Claude Code vor dem Lauf, ab
Version 2.1.153.
Die Zeile verschwindet während Autocomplete, Hilfemenü und Permission-Prompts. Ausserhalb von Fullscreen teilen sich Notifications dieselbe Zeile und schneiden dich auf einem schmalen Terminal ab.
Was sich zu zeigen lohnt
Das JSON ist gross. Auf einer 80-Zeichen-Zeile ist das meiste davon Rauschen.
Kontext. context_window.used_percentage ist das Feld. Es wird nur aus
Input-Tokens gerechnet (input_tokens + cache_creation_input_tokens + cache_read_input_tokens). Output-Tokens stecken nicht drin. Rechnest du den
Prozentsatz selbst aus current_usage, nimm dieselbe Formel, sonst
widersprichst du /context. current_usage ist null vor dem ersten
API-Call und nach /compact wieder, bis die nächste Antwort es füllt. Dasselbe
gilt früh in der Session für die Prozentfelder. Fallback in jq: // 0.
Das Standardfenster ist 200k Tokens, oder 1M bei Modellen mit extended
context. exceeds_200k_tokens ist eine feste Schwelle, nicht «das Fenster
ist voll».
Kosten. cost.total_cost_usd ist eine Schätzung auf dem Client. Das ist
nicht deine Rechnung. Bei /clear fällt sie auf 0 $ (vor v2.1.211 hat sie
sich mitgeschleppt, das war verwirrend). total_duration_ms ist Wanduhr seit
Sessionstart; total_api_duration_ms ist die Wartezeit auf die API.
Rate Limits. rate_limits.five_hour und rate_limits.seven_day gibt es
für Claude.ai Pro/Max nach der ersten API-Antwort. Jedes Fenster kann für sich
fehlen. Fehlend heisst «Segment nicht zeichnen», nicht 0 %.
Git. Im JSON steht kein git.branch. git rufst du selbst auf. Das ist
in einem grossen Repo langsam — und das Skript läuft oft, also cachen. Nimm
session_id in den Cache-Dateinamen, nicht $$ oder os.getpid(): die
ändern sich bei jedem Aufruf, der Cache trifft nie. Gleichzeitige Sessions in
verschiedenen Repos dürfen sich keine Datei teilen.
workspace.git_worktree ist gesetzt, wenn du in einem linked Worktree bist.
worktree.* ist ein anderes Objekt und nur während einer
Claude-Code-Worktree-Session da.
Was ich sonst noch nutze. model.display_name plus effort.level.
workspace.current_dir (gleicher Wert wie cwd; nimm das verschachtelte
Feld). pr.number und pr.review_state, wenn auf dem Branch eine PR oder
eine GitLab-Merge-Request offen ist. session_name, wenn du die Session
benannt hast; der Default im Stil my-app-3f füllt das Feld nicht.
vim.mode, wenn du den Vim-Modus nutzt.
Ein fehlendes Feld ist nicht dasselbe wie ein Feld mit null. Beides
abfangen.
Ein erstes Skript
Bash, auf macOS und Linux. Ausführbar machen (chmod +x ~/.claude/statusline.sh)
und command darauf zeigen.
#!/bin/bash
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
echo "[$MODEL] ${DIR##*/} | ${PCT}% context"Testen, ohne Claude Code zu öffnen:
echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test"}' | ./statusline.shDie gedruckte Zeile kurz halten. Die Bar ist nicht breit — und auf einem schmalen Terminal fressen die Notifications rechts sie auf.
Wenn sie leer bleibt
Die übliche Liste, in der Reihenfolge, in der ich prüfe:
- Ist das Skript ausführbar und schreibt es auf stdout statt stderr?
- Hast du den Workspace-Trust-Dialog bestätigt? Bis dahin bleibt die Zeile
leer und
claude --debugloggt, dass der Befehl übersprungen wurde. - Unter Windows: hat Git Bash einen Backslash im Pfad gefressen?
- Schaust du auf Nulls vor der ersten Antwort? Fallbacks, dann eine Runde warten.
- Hat jemand
disableAllHooksgesetzt? Ausserhalb von Managed Settings schaltet das eine User-Statuszeile ab.allowManagedHooksOnlyin organisationsverwalteten Settings ignoriert deine lautlos.
claude --debug loggt Exit-Code und stderr des ersten Aufrufs in einer
Session. Claude den statusLine-Befehl gegen deine Settings-Datei ausführen
zu lassen, bringt den Fehler auch schneller als eine leere Zeile anzustarren.
Wenn nach mehreren Messages -- oder leere Werte stehen, einmal neu starten.
Wenn OSC-8-Links als Text gerendert werden, Terminal prüfen, dann
FORCE_HYPERLINK=1 claude versuchen.
Was ich wirklich laufen lasse
Drei Zeilen. Verzeichnis und Branch links, eine Uhr rechts. Modell links in Zeile zwei, Kontext rechts. Das 5-Stunden-Limit in Zeile drei, das 7-Tage-Limit gegenüber. Das ist das Preset «Mine» im Baukasten. Mir geht es darum, vor dem vollen Fenster zu kompaktieren — und um die Wochenkappe vor Freitagmittag.
Das Skript checke ich trotzdem nicht ein. Will ich ein anderes Segment, füge ich einen neuen Prompt ein. Im Baukasten entscheide ich das Layout. Claude Code schreibt die Datei.
Wenn die Fusszeile klickbare Badges wachsen soll, sobald in der Konversation
eine ID auftaucht, ist das eine andere Einstellung: footerLinksRegexes.
Kein Skript.
Wenn eine Statuszeile sich mit dir anlegt und du den Abend nicht mit ANSI-Codes verbringen willst: schreib mir. Manchmal reicht eine 40-Zeichen-jq-Zeile.