Max Nardit
← Artikel
7 mindev

Claude-Code-Statusline und ccusage: die nativen Rate Limits und ein 5-Stunden-Block, der zu einer anderen Zeit endet

Zwei Tools beantworten dieselbe Frage mit unterschiedlichen Belegen: Der Server meldet eine Reset-Zeit, ein Log-Parser leitet eine ab. Liegen sie Stunden auseinander, glaub dem Server und behalte den Parser für die Historie. Der Dollarbetrag ist im Abo ein Wechselkurs.

Inhalt

In der Statusline von Claude Code standen früher der Modellname und ein Git-Branch. Seit das Objekt rate_limits auf ihrem stdin ankommt, kann sie einem Pro- oder Max-Abonnenten auch die zwei Zahlen zeigen, die die Arbeit stoppen: wie viel vom 5-Stunden-Fenster und vom 7-Tage-Fenster schon verbraucht ist und wann jedes zurückgesetzt wird. Ältere Anleitungen verweisen auf ccusage, das Kosten und eigene 5-Stunden-Blöcke aus den JSONL-Transkripten auf deiner Festplatte berechnet. Das sind verschiedene Messungen, nicht dieselben Zahlen auf einem anderen Weg.

Beide sind nützlich, messen aber nicht dasselbe. Ich habe eine interaktive Session mit einer Statusline laufen lassen, die alles protokolliert hat, was Claude Code ihr geschickt hat, und genau diesen Input dann an ccusage statusline gegeben. Claude Code meldete, dass das 5-Stunden-Fenster um 04:30 UTC zurückgesetzt wird. ccusage meldete um 03:58 UTC, sein 5-Stunden-Block habe noch 3 Stunden und 1 Minute übrig. Eine Differenz von etwa zweieinhalb Stunden, auf derselben Maschine, in derselben Minute. Im Folgenden: was jedes Feld abdeckt, das Skript, das ich verwendet habe, und warum die beiden auseinanderliegen können.

Welche Nutzungsdaten bekommt die Claude-Code-Statusline?

Ein JSON-Objekt auf stdin, bei jeder Aktualisierung der Zeile, mit vier Gruppen, die für die Nutzung zählen: rate_limits, context_window, cost und prompt_cache. Nur die erste beschreibt deinen Plan. Die anderen beschreiben die aktuelle Session.

Laut der Statusline-Doku enthalten rate_limits.five_hour und rate_limits.seven_day jeweils used_percentage (0 bis 100) und resets_at in Unix-Epoch-Sekunden. In meiner Session kamen sie als ganze Zahlen an. context_window liefert used_percentage des Modellfensters, berechnet nur aus Input-Tokens, dazu context_window_size, hier 1.000.000 für Haiku 5.5, also bedeuteten „7%“ etwa 65.000 Tokens. cost.total_cost_usd ist eine Schätzung für die Session. Das Objekt prompt_cache meldet Trefferquote, Misses und ob der Cache warm ist; es sind dieselben Daten, die /usage zeigt, und genau darauf solltest du achten, wenn du herausfinden willst, was deinen Prompt-Cache kaputt gemacht hat.

Die Zeile läuft beim Start der Session und wird unter anderem neu ausgeführt, wenn eine neue Assistant-Nachricht ankommt, nach /compact, wenn ein rate_limits-Fenster im letzten Input sein resets_at erreicht, wenn ein warmer Prompt-Cache sein expires_at erreicht, und bei jedem refreshInterval-Tick, falls du einen setzt. Updates werden mit 300 ms entprellt. Auf meinem stdin unter 2.1.302 tauchte ein zusätzlicher Schlüssel auf, den die Tabelle in der Doku nicht nennt, scratchpad_dir. Verlass dich nicht darauf.

Wann fehlen die rate_limits-Felder in Claude Code?

Beim ersten Durchlauf jeder Session, bei allen, die weder ein Abo haben noch hinter einem Gateway mit Ausgabenlimit sitzen, und, Fenster für Fenster, sobald eine Reset-Zeit vorbei ist. Laut Doku erscheint rate_limits „only for claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit for you, and only after the first API response in the session“, und Claude Code lässt ein Fenster weg, sobald sein resets_at vorbei ist.

Der Start-Durchlauf in meinem Log passte genau dazu:

first run:   rate_limits absent, prompt_cache absent, context_window.used_percentage null, cost 0
after reply: rate_limits.five_hour 4% (resets 04:30 UTC), seven_day 45%, context 6%

Ein Skript, das direkt auf rate_limits.five_hour zugreift, stürzt also beim Start ab, und du siehst nur eine leere Statusline, weil Claude Code die Ausgabe erst anzeigt, wenn das Skript mit Exit-Code 0 endet. Jedes dokumentierte Feld, das fehlen oder null sein kann, braucht einen Fallback.

Wie baust du eine Claude-Code-Statusline aus den nativen Feldern?

Stdin einmal lesen, eine Zeile ausgeben und die Nutzungsfelder als optional behandeln. Das Rate-Limit-Beispiel in der Doku gibt die zwei Prozentwerte aus. Meins, in Python 3 nur mit der Standardbibliothek, ergänzt die Restzeit jedes Fensters und den Kontextwert. Um genau diese Zahlen geht es im Rest des Artikels:

python
#!/usr/bin/env python3
import json, sys, time
 
data = json.load(sys.stdin)
parts = [(data.get("model") or {}).get("display_name", "?")]
 
ctx = (data.get("context_window") or {}).get("used_percentage")
parts.append(f"ctx {ctx:.0f}%" if ctx is not None else "ctx --")
 
now = time.time()
for key, label in (("five_hour", "5h"), ("seven_day", "7d")):
    win = (data.get("rate_limits") or {}).get(key) or {}
    pct, reset = win.get("used_percentage"), win.get("resets_at")
    if pct is None or reset is None or reset <= now:
        continue
    left = int(reset - now)
    if left >= 86400:
        eta = f"{left // 86400}d{left % 86400 // 3600}h"
    else:
        eta = f"{left // 3600}h{left % 3600 // 60:02d}m"
    parts.append(f"{label} {pct:.0f}% ({eta})")
 
print(" | ".join(parts))

Speichere es als ~/.claude/statusline.py, mach es mit chmod +x ausführbar und füg das in ~/.claude/settings.json ein:

json
{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.py",
    "refreshInterval": 60
  }
}

refreshInterval ist für den Countdown da. Ohne ihn läuft die Zeile nur bei Ereignissen neu, und eine untätige Session zeigt die Restzeit vom Stand ihrer letzten Nachricht. Für den ersten und den letzten der fünf protokollierten Payloads, um 04:07 UTC erneut abgespielt, gab das Skript aus:

Haiku 5.5 | ctx --
Haiku 5.5 | ctx 7% | 5h 7% (0h22m) | 7d 45% (3d16h)

Teste deins genauso, bevor du ihm traust. Setz command für eine Session auf tee -a ~/.claude/statusline-input.jsonl | ~/.claude/statusline.py, schick ein paar Nachrichten, stell den einfachen Befehl wieder her und spiel eine beliebige Zeile mit tail -1 ~/.claude/statusline-input.jsonl | ~/.claude/statusline.py ab. Das Überspringen abgelaufener Fenster ist hier wichtig, denn die Reset-Zeiten eines erneut abgespielten Payloads liegen in der Vergangenheit. Noch etwas aus der Doku: In einem Ordner, dem du nicht vertraut hast, bleibt die Statusline leer, bis du den Trust-Dialog bestätigst, weil sie einen Shell-Befehl nach derselben Regel wie Hooks ausführt.

Was ergänzt ccusage statusline zu den eigenen Feldern von Claude Code?

Historie. ccusage liest die lokalen Transkripte und ergänzt drei Zahlen, die Claude Code nicht auf stdin legt: die Tagessumme über alle Sessions, die Kosten des aktuellen 5-Stunden-Blocks mit Restzeit und eine Burn Rate. Um 03:58 UTC lautete seine Zeile mit --cost-source both (Dollarbeträge für Tag und Block ersetzt):

🤖 Haiku 5.5 (medium) | 💰 ($0.01 cc / $0.01 ccusage) session / $X today / $Y block (3h 1m left) | 🔥 $Z/hr | 🧠 64,531 (6%)

Der Session-Wert stammt standardmäßig nicht von ccusage selbst. Mit --cost-source auto, dem Default, zeigt es cost.total_cost_usd von Claude Code und rechnet nur selbst, wenn dieses Feld fehlt; ccusage erzwingt die eigene Berechnung, und both gibt beide nebeneinander aus. Die Preise kommen aus einer zwischengespeicherten LiteLLM-Tabelle (offline ist der Default), und das Output-Caching ist standardmäßig an, mit einer Cache-Gültigkeit von einer Sekunde, was nichts mit refreshInterval von Claude Code zu tun hat. Die Seite kennzeichnet das Feature als Beta. Ich habe es als bun x ccusage@20.0.35 statusline ausgeführt; npx -y ccusage statusline ist die dokumentierte Alternative.

Was ccusage gar nicht zeigt, ist der 7-Tage-Prozentwert. Nichts in den lokalen Logs verrät ihm dein Wochenkontingent, also fehlt ihm der Nenner dafür.

Stimmt der 5-Stunden-Block von ccusage mit dem five_hour-Fenster von Claude Code überein?

In meinem Test nicht. Die beiden waren sich uneinig, wann das Fenster endet, und genau danach plant ein Abonnent.

03:58 UTC   Claude Code  rate_limits.five_hour.resets_at = 04:30 UTC  (32 min left)
03:58 UTC   ccusage      block: 1h 58m elapsed, 3h 1m remaining       (ends about 07:00 UTC)

In diesem Lauf begann der ccusage-Block um 02:00, fast zwei Stunden bevor meine Test-Session begann. Er zählte also schon andere Aktivität auf dieser Maschine mit. Die Doku zum Blocks-Report beschreibt einen Block als „Triggered by your first message“, der „Lasts exactly 5 hours from start time“, und behauptet über ihn:

Matches Claude's internal session tracking

Warum sie auseinanderliefen, habe ich nicht nachverfolgt, und mein Test kann zwei Kandidaten nicht trennen. Erstens: Bei gleichmäßiger Nutzung bleiben eine Kette von 5-Stunden-Fenstern, die an einem Zeitpunkt verankert ist, und eine, die an einem anderen verankert ist, für immer versetzt, selbst wenn beide jede Anfrage sehen. Zweitens: ccusage sieht nur die Transkripte in seinen Datenverzeichnissen, während der Server die Nutzung des Kontos auch von anderen Geräten und von claude.ai misst. So oder so deckte der Dollarbetrag des Blocks in diesem Lauf andere fünf Stunden ab als die, die Anthropic gemessen hat, und seine „verbleibende Zeit“ lag zweieinhalb Stunden daneben. Ein Test auf einer Maschine sagt dir nicht, wie oft das passiert. Er sagt dir aber, welcher Zahl du glauben solltest, wenn sie sich widersprechen: resets_at ist die eigene Antwort des Servers.

Stimmen die Session-Kosten von Claude Code und von ccusage überein?

Nah dran, aber nicht gleich, und für einen Abonnenten ist keiner davon eine Rechnung. Für dieselbe Session meldete Claude Code total_cost_usd 0.01257, und ccusage session berechnete aus dem Transkript 0.01149, etwa 9% weniger. Beide erschienen in der Zeile als $0.01, also zeigte --cost-source both überhaupt keinen Unterschied.

Die Differenz habe ich nicht nachverfolgt. Kandidaten sind eine andere Preistabelle oder Anfragen, die nie im Transkript landen; die Kosten-Seite nennt Hintergrundanfragen wie Prompt-Vorschläge und das Zusammenfassen von Unterhaltungen. Was die Doku klärt, ist die Bedeutung: Claude Code berechnet den Wert „locally from token counts at list price“, und für Pro- und Max-Abonnenten gilt: „the session cost figure isn’t relevant for billing purposes“. Im Abo ist der Dollarbetrag eher ein Wechselkurs: Er zeigt API-äquivalente Ausgaben. Das ist nützlich, um Sessions zu vergleichen oder zu wissen, was eine Last mit API-Credits kosten würde, aber nicht, um zu wissen, wann du gesperrt wirst.

Welche Claude-Code-Nutzungszahlen sind kontoweit und welche lokal?

Nur rate_limits ist kontoweit. Alles andere ist enger gefasst:

rate_limits.five_hour / seven_day   the account, as the server sees it
cost.total_cost_usd                  this session, list price, reset by /clear
context_window                       this session's last API response
ccusage today / block / burn rate    local transcripts, filtered to the day or block
/usage plan breakdown                local session history on this machine

Laut Kosten-Seite ist die Aufschlüsselung in /usage „computed from local session history on this machine, so usage from other devices or claude.ai is not included“, und ccusage ist schon von der Bauart her lokal. Das zählt am meisten, wenn du viele Sessions gleichzeitig laufen lässt. Die rate_limits-Prozentwerte aller Sessions beschreiben dasselbe Kontingent; Kosten und Kontext pro Session sagen dir etwas über eine Session und nichts darüber, wie nah das Konto an seinem Limit ist. Laut Anthropics Help Center werden die Pro- und Max-Limits geteilt zwischen Claude und Claude Code, also bewegt auch Chat-Nutzung rate_limits, während weder ccusage noch die Aufschlüsselung in /usage sie sieht.

Der native stdin hat außerdem kein modellspezifisches Fenster. Wenn dein Plan ein eigenes wöchentliches Sonnet-Limit hat, kann die Statusline es nicht zeigen. Diese Lücke deckt Claude Usage Deck im Browser ab: Es liest deine Nutzungslimits auf claude.ai und zeigt die Anzeigen für Session, Woche und Sonnet-Woche.

Was sollte eine Claude-Code-Statusline im Pro- oder Max-Plan zeigen?

Die zwei rate_limits-Prozentwerte mit ihren Reset-Zeiten, und den Kontext. Das sind die Zahlen, die bestimmen, was du als Nächstes tust: ob du eine lange Aufgabe jetzt startest oder bis 04:30 wartest, und wann du /compact ausführst. Der Dollarbetrag gehört nur dann in die Zeile, wenn du pro Token zahlst, und selbst dann ist er eine Schätzung, zum Listenpreis oder zu deinen vertraglichen Preisen, falls ein Admin modelPricing gesetzt hat.

Behalte ccusage, aber für das, was es gut kann. ccusage claude weekly --last 1 --mode calculate beantwortet „was haben meine Claude-Code-Transkripte hier diese Woche zu API-Preisen verbraucht“ (Wochen beginnen am Sonntag in deiner Systemzeitzone, es sei denn, du übergibst --start-of-week und --timezone), und das ist eine echte Frage, wenn du entscheidest, ob eine Last überhaupt in ein Abo gehört. Es ist dieselbe Frage, die hinter Anthropics Plan stand, programmatische Nutzung separat zu messen. Was ein Log nicht kann, ist dir zu sagen, wo das Fenster des Servers endet. Dafür liegt die Antwort schon auf stdin.

01 / Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit@mnardit

02 / Weitere Artikel