ai newsMo., 26. Okt.

Claude Code Hooks: Diese Events treiben den Tokenverbrauch nach oben, diese senken ihn

Die Claude Code Hooks Referenz listet inzwischen 33 Events, von SessionStart bis ElicitationResult. Viele nutzen Hooks zunächst für simple Beispiele wie Linter-Aufrufe nach jedem Edit, doch der Tokenverbrauch variiert stark je nach eingesetztem Event.

dev.toMontag 02:00 · 26.10.DE12 Min. Lesezeit
Bild: dev.to

Veröffentlicht am 26. Okt. • Zuerst veröffentlicht auf tokenade.net

Die Claude Code Hooks-Referenz listet inzwischen 33 Ereignisse auf, von SessionStart bis ElicitationResult (Anthropic Hooks Reference, abgerufen am 24.09.2026). Die meisten Menschen begegnen Hooks erstmals über eine Liste mit zehn Copy-Paste-Beispielen: ein Linter nach jeder Bearbeitung, ein Desktop-Ping, eine Absicherung gegen rm -rf. Dann lesen sie, dass ein Hook den Token-Verbrauch jemandes um 42 % oder 90 % gesenkt hat, und fragen sich, ob ihre eigenen Hooks eher helfen oder schaden.

Beides kommt vor. Ein Hook, der bei UserPromptSubmit auf stdout schreibt, fügt seinen Text bei jedem gesendeten Prompt der Konversation hinzu. Ein Hook, der einen Toolaufruf umschreibt, bevor er ausgeführt wird, kann verhindern, dass ein 3.000 Zeilen langes Log überhaupt das Modell erreicht.

Der Unterschied liegt in zwei Details, die die Beispiellisten auslassen: welchem Ereignis der Hook lauscht und welches JSON-Feld er zurückgibt. Dieser Artikel ordnet jedes token-relevante Ereignis dem zu, was es hinzufügt oder entfernt, und stützt sich dabei nur auf die offizielle Referenz. Zusätzlich werden die Fallen aufgelistet, durch die ein Hook stillschweigend nichts tut.

Ein Hook ist ein Handler, den Claude Code an einem festen Punkt einer Session ausführt: ein Shell-Befehl, ein HTTP-Endpunkt, ein Tool auf einem verbundenen MCP-Server, ein LLM-Prompt oder ein Subagent (Referenz). Die Referenz gruppiert die Ereignisse nach ihrem Takt. SessionStart und SessionEnd werden einmal pro Session ausgelöst. UserPromptSubmit, Stop und StopFailure werden einmal pro Zug ausgelöst. PreToolUse und PostToolUse werden bei jedem Toolaufruf innerhalb der agentischen Schleife ausgelöst, sodass ihre Anzahl mit jedem gelesenen File, jeder Suche und jedem Befehl steigt, den der Agent ausführt.

Dieser Takt ist die erste Hälfte der Kostenfrage. Ein Handler, der einmal pro Session ausgelöst wird und 400 Zeichen einfügt, kostet diese Zeichen genau einmal, plus deren erneutes Lesen bei jeder späteren Anfrage. Dieselben 400 Zeichen bei PreToolUse landen neben jedem Toolergebnis in der Session. Alles, was in die Konversation gelangt, wird bei jeder folgenden Modellanfrage zusammen mit dem restlichen Verlauf erneut gesendet. Genau deshalb ist Prompt-Caching für Agents so wichtig: zwischengespeichertes erneutes Lesen ist günstiger als frischer Input, aber nie kostenlos.

Die zweite Hälfte ist das, was der Handler zurückgibt. Ein Hook kann vier Dinge mit Tokens tun:

Der Rest dieses Artikels sortiert die Ereignisse in diese vier Kategorien. Wenn Sie noch nie gemessen haben, wohin die Tokens Ihrer Session fließen, tun Sie das zuerst. Der Artikel zum Messen der Token-Nutzung von Agents zeigt, wie man die Usage-Felder im Session-Transkript liest.

Die Regel von Claude Code für reinen stdout ist kurz. Bei Exit 0 gilt: "Bei den meisten Ereignissen schreibt Claude Code stdout in das Debug-Log und zeigt es nicht im Transkript an. Die Ausnahmen sind UserPromptSubmit, UserPromptExpansion, SessionStart und PostModelSwitch, wo Claude Code reinen stdout als Kontext hinzufügt, den Claude sehen und berücksichtigen kann" (Referenz, Exit-Code 0).

Ein echo in einem SessionStart-Hook ist also eine kleine, einmalige Ergänzung. Dasselbe echo in einem UserPromptSubmit-Hook ist eine Ergänzung bei jedem Prompt. Die Referenz weist auch darauf hin, dass weder reiner stdout noch additionalContext bei UserPromptSubmit einen sichtbaren Transkript-Eintrag erzeugt: Beides wird als Systemerinnerung eingefügt, die mit dem Namen des Hooks beginnt. Man zahlt für Text, den man nie auf dem Bildschirm sieht, was der häufigste Weg ist, wie ein Hook-Setup eine Rechnung wachsen lässt, ohne dass es jemand bemerkt.

additionalContext ist die strukturierte Variante und funktioniert bei mehr Ereignissen. Bei PreToolUse und PostToolUse ist es ein "String, der neben dem Toolergebnis zu Claudes Kontext hinzugefügt wird." Bei SessionStart wird er "zu Beginn der Konversation, vor dem ersten Prompt" hinzugefügt. Bei SubagentStart und PostModelSwitch ist es das Einzige, was der Hook tun kann: Kontext, ohne Blockierung (Referenz, Decision Control). Wenn mehrere Hooks additionalContext für dasselbe Ereignis zurückgeben, erhält Claude alle Werte.

Ein dritter Weg ist weniger offensichtlich. Bei PostToolUse und PostToolUseFailure zeigt Exit 2 laut Referenz "stderr Claude; das Tool wurde bereits ausgeführt." Die Referenz empfiehlt dies als Weg, eine Warnung aus diesen Ereignissen an die Oberfläche zu bringen. Jede Warnung ist Text in der Konversation, daher fügt ein Linter-Hook, der bei jeder Bearbeitung mit Exit 2 und 80 Zeilen Befunden endet, 80 Zeilen pro Bearbeitung hinzu.

Stop-Hooks kosten anders. Exit 2 bei Stop "verhindert, dass Claude stoppt, und setzt die Konversation fort." Das ist ein kompletter neuer Modellzug, mit dem gesamten erneut gesendeten Verlauf. Eine Prüfung, die bei jedem Zug fehlschlägt, hält die Session am Laufen, bis man eingreift. Die prompt-basierte Variante hat dafür eine Sicherung: Wenn das Modell impossible mit ok: false zurückgibt, lässt Claude Code den Zug enden, statt den Grund zurückzuspielen. Command-Hooks haben keine solche Sicherung; die Schleife muss man selbst begrenzen.

Das Muster, das man beibehalten sollte: Kontext-Injektion ist in Ordnung, wenn der Text kurz ist, den nächsten Schritt des Modells verändert und mit dem niedrigsten Takt ausgelöst wird, der den Zweck noch erfüllt. Ein Branch-Name und das offene Issue bei SessionStart bestehen diesen Test. Ein 2.000 Zeichen langer Style-Guide bei jedem UserPromptSubmit gehört in CLAUDE.md, wo er wenigstens Teil des zwischengespeicherten Präfixes ist.

Zwei Felder ermöglichen es einem Hook, das, was das Modell liest, zu verkleinern, und sie wirken an entgegengesetzten Enden eines Toolaufrufs.

updatedInput bei PreToolUse "ändert die Eingabeparameter des Tools vor der Ausführung. Ersetzt das gesamte Eingabeobjekt, daher sind unveränderte Felder zusammen mit den geänderten mitzuliefern" (Referenz, PreToolUse Decision Control). Hier arbeiten die meisten tokensparenden Hooks.

Ein Bash-Aufruf von npm test kann so umgeschrieben werden, dass er durch einen Filter geleitet wird, der nur Fehler behält; ein Read einer 5.000 Zeilen langen Datei kann mit offset und limit eingeschränkt werden; ein Grep ohne Pfad kann auf src/ begrenzt werden. Das Modell liest nur das gekürzte Ergebnis, und nichts Zusätzliches gelangt in den Kontext. Zwei Details aus der Referenz sind hier wichtig.

Berechtigungsregeln werden "gegen den Input geprüft, den der Hook zurückgibt, nicht gegen den Input, den Claude gesendet hat", sodass eine Umschreibung keinen Befehl an einer Deny-Regel vorbeischmuggeln kann. Und die Ersetzung ist ein Ganzes-Objekt-Ersatz: Vergisst man ein Feld, läuft das Tool ohne es.

updatedToolOutput bei PostToolUse "ersetzt die Ausgabe des Tools durch den bereitgestellten Wert, bevor er an Claude gesendet wird. Der Wert muss der Ausgabeform des Tools entsprechen." Die Referenz empfiehlt es inzwischen gegenüber dem älteren updatedMCPToolOutput, das nur bei MCP-Tools funktioniert. Dieses Feld sollte man verwenden, wenn man im Voraus nicht wissen kann, wie groß ein Ergebnis wird: Man lässt den Aufruf laufen und behält dann die Fehlerzeilen eines Build-Logs, entfernt die Fortschrittsbalken oder kürzt eine JSON-Antwort auf die Schlüssel, die das Modell braucht. Das Referenzbeispiel ersetzt den stdout eines Bash-Ergebnisses durch einen geschwärzten String, was die Form zeigt.

Man beachte, was decision: "block" bei PostToolUse tut und was nicht: Es "fügt den Grund neben dem Toolergebnis hinzu. Claude sieht weiterhin die ursprüngliche Ausgabe; um sie zu ersetzen, verwendet man updatedToolOutput." Ein blockierender PostToolUse-Hook, der eine riesige Ausgabe verbergen soll, fügt Text hinzu und entfernt nichts.

Die veröffentlichten Ergebnisse zu diesem Ansatz sind Single-Team-Benchmarks, daher sollte man sie als Größenordnungen lesen. SitePoint berichtete am 20.08.2026, dass Graft, ein Hook-basiertes Framework, die durchschnittliche Token-Nutzung pro Aufgabe in internen SWE-bench Verified-Läufen seiner Autoren von 8.070 auf 4.650 senkte, ein Rückgang von 42 % (SitePoint). Spotify Engineering veröffentlichte im September 2026 den Bericht eines Engineers mit dem Titel "Portal by Spotify cut my Claude Code token usage by 90%", in dem argumentiert wird, dass die meisten dessen, was ein Coding-Agent tut, I/O ist, etwa das Lesen von fünf Dateien, um eine Frage zu einer einzelnen Methode zu beantworten (Spotify Engineering).

Beides ist kein kontrollierter Test im eigenen Repository. Der Mechanismus hinter beiden ist der oben beschriebene, und die allgemeine Technik hat eine eigene Seite: Output-Filtering für Command-Logs.

Jeder String, den ein Hook einfügt, hat ein hartes Limit. Aus der Referenz: "Die Strings additionalContext, systemMessage und initialUserMessage eines Hooks sowie sein reiner stdout sind auf 10.000 Zeichen begrenzt." Jedes JSON-Feld wird separat gemessen, reiner stdout wird als Ganzes gemessen, und mehrere Hooks bei einem Ereignis werden jeweils einzeln gemessen (Referenz, JSON Output).

Über dem Limit schneidet Claude Code nicht ab. Es "speichert die Ausgabe in einer Datei im Session-Verzeichnis und ersetzt sie durch den Dateipfad und eine Vorschau der ersten bis zu 2.000 Zeichen." Claude kann diese Datei lesen, aber Claude Code fordert es nicht dazu auf. Große, gültige Bash-Ergebnisse werden auf dieselbe Weise behandelt. Anders als bei der Bash-Obergrenze gibt es beim Hook-Limit "keine Einstellung oder Umgebungsvariable, um es zu erhöhen."

Wenn ein Hook regelmäßig mehr als ein paar hundert Zeichen erzeugt, ist das bessere Design meist, die Details selbst in eine Datei zu schreiben und nur eine Zeile mit einem Verweis darauf einzufügen.

Prompt-basierte Hooks (type: "prompt") "senden den Hook-Input und Ihren Prompt an ein Claude-Modell, standardmäßig Haiku"; das Feld model überschreibt dies (Referenz, prompt-basierte Hooks). Agent-basierte Hooks gehen weiter und führen einen Subagenten aus, der vor der Entscheidung Tools verwenden kann. Beides sind abgerechnete Anfragen zusätzlich zu Ihrer Haupt-Session.

Das verändert die Rechnung bei hochfrequenten Ereignissen. Ein Prompt-Hook bei Stop läuft einmal pro Zug, was meist tolerierbar ist. Ein Prompt-Hook bei PreToolUse löst eine Modellanfrage bei jedem Toolaufruf aus, und jede trägt den Hook-Input, einschließlich der vollständigen Tool-Argumente. Für eine Prüfung, die ein regulärer Ausdruck oder jq erledigen kann, kostet ein Command-Hook keine Tokens und antwortet in Millisekunden.

Setzen Sie Prompt- und Agent-Hooks dort ein, wo eine Bewertung im Mittelpunkt steht: etwa bei der Entscheidung, ob eine Aufgabe wirklich abgeschlossen ist, bevor Claude stoppen darf, oder ob ein Diff etwas beruehrt, das eine Regel verbietet. Lassen Sie sie vom Pfad der einzelnen Tool-Aufrufe fern, solange Sie die zusaetzlichen Anfragen nicht gemessen haben. Die Kosten agentischen Codierens folgen derselben Logik: Jeder frische Modellkontext beginnt mit seinem eigenen Overhead.

Lange galt die Beschwerde aus dem GitHub Issue #11008, das im November 2025 eroeffnet wurde: Hooks erhielten keine Token- oder Kostendaten, sodass ein Budget-Hook das Transkript selbst parsen musste. PreModelSwitch aendert das fuer einen teuren Moment. Sein Input enthaelt context_tokens (die Token, die die naechste Anfrage erneut als Prompt sendet), prompt_cache_warm, cache_ttl und estimated_cache_write_usd, die geschaetzten Kosten fuer das Schreiben dieses Kontexts in den Cache des neuen Modells (Referenz: PreModelSwitch Input).

Warum das wichtig ist: Ein Modellwechsel macht einen warmen Prompt-Cache zunichte. Das Beispiel in der Referenz zeigt eine /model opus Anfrage aus einer Sonnet 5 Sitzung mit 182.340 Kontext-Token und geschaetzten Cache-Schreibkosten von 1,14 US-Dollar zum Listenpreis. Ein Hook kann permissionDecision: "ask" mit diesem Betrag als Begruendung zurueckgeben, sodass der Wechsel mit dem Preis vor Augen geschieht. Das Standard-Timeout fuer dieses Ereignis betraegt 30 Sekunden, und anders als bei PreToolUse blockiert ein PreModelSwitch-Hook, der das Timeout ueberschreitet, den Wechsel.

Bei allen anderen Ereignissen bleibt das Transkript die Quelle. Jeder Hook erhaelt transcript_path, und die Referenz weist darauf hin, dass die Datei asynchron geschrieben wird und dem im Speicher gehaltenen Gespraech hinterherhinken kann. Ein Kosten-Hook, der sie liest, muss damit rechnen, dass der letzte Zug fehlt. Fuer Zahlen pro Sitzung, ohne einen Hook zu schreiben, siehe die Anleitung zum Reduzieren der Claude Code Token-Nutzung, die die vorhandenen Zaehler abdeckt.

Was sind Claude Code Hooks? Hooks sind Handler, die Claude Code automatisch an festen Punkten einer Sitzung ausfuehrt: vor und nach Tool-Aufrufen, beim Absenden eines Prompts, beim Start oder Ende einer Sitzung, vor der Kompaktierung und bei rund dreiцig weiteren Ereignissen. Ein Handler kann ein Shell-Befehl, ein HTTP-Endpunkt, ein MCP-Tool, ein LLM-Prompt oder ein Subagent sein. Er erhaelt das Ereignis als JSON (bei Befehlen ueber stdin) und kann eine Entscheidung, zusaetzlichen Kontext oder ein umgeschriebenes Tool-Input oder -Output zurueckgeben. Konfiguriert werden sie in Settings-Dateien oder ueber das /hooks Menue.

Verbrauchen Claude Code Hooks Token? Nur wenn ihre Ausgabe in das Gespraech einfliesst oder sie ein Modell aufrufen. Bei den meisten Ereignissen geht die stdout eines erfolgreichen Hooks ins Debug-Log und kostet nichts. Reine stdout bei UserPromptSubmit, UserPromptExpansion, SessionStart und PostModelSwitch wird dem Kontext hinzugefuegt, additionalContext wird ueberall dort ergaenzt, wo es unterstuetzt wird, und stderr eines PostToolUse-Hooks mit Exit-Code 2 wird Claude angezeigt. Prompt- und Agent-Hooks sind eigene Modellanfragen, standardmaessig Haiku bei Prompt-Hooks.

Kann ein Hook die Token-Nutzung von Claude Code reduzieren? Ja, ueber zwei Felder. updatedInput bei PreToolUse schreibt einen Tool-Aufruf um, bevor er ausgefuehrt wird, etwa indem es eine Dateilesung einschraenkt oder einen Testbefehl durch einen Filter leitet. updatedToolOutput bei PostToolUse ersetzt das Ergebnis eines Tools, bevor Claude es liest. Veroeffentlichte Ergebnisse stammen aus Benchmarks einzelner Teams: SitePoint berichtete im August 2026 von einem Rueckgang von 42 Prozent pro Aufgabe fuer Graft bei eigenen SWE-bench Durchlaeufen der Autoren. Messen Sie in Ihren eigenen Sitzungen, bevor Sie einer Zahl vertrauen.

Was ist der Unterschied zwischen PreToolUse und PostToolUse? PreToolUse wird vor der Ausfuehrung eines Tool-Aufrufs ausgeloest und kann ihn blockieren, eine Bestaetigung anfordern oder seine Argumente mit updatedInput umschreiben. PostToolUse wird nach einer erfolgreichen Ausfuehrung ausgeloest; es kann den Aufruf nicht rueckgaengig machen, aber Kontext hinzufuegen, ueber Exit-Code 2 eine Warnung anzeigen oder das Ergebnis mit updatedToolOutput ersetzen. Fuer Token-Einsparungen vermeidet PreToolUse eine grosse Ausgabe von vornherein, waehrend PostToolUse Ausgaben verarbeitet, deren Groesse sich nicht vorhersagen laesst.

Warum blockiert mein Hook nichts? Die ueblichen Ursachen sind Exit-Code 1 statt 2, ein nicht existierender Skriptpfad, ein Matcher, der kein Tool trifft (etwa ein MCP-Praefix ohne.*), Text, der vor dem JSON auf stdout ausgegeben wird, oder ein PreToolUse-Hook, der das Timeout ueberschritten hat. Die Referenz stellt ausdruecklich klar, dass ein Befehls-Hook mit Timeout bei PreToolUse den Tool-Aufruf weiterlaufen laesst. Aktivieren Sie das Debug-Logging, um die Parse- oder Validierungsmeldung zu sehen, und testen Sie jede Sperre mit einem Aufruf, den sie stoppen sollte.

Gibt es eine Groessenbegrenzung fuer Hook-Ausgaben? Ja. additionalContext, systemMessage, initialUserMessage und reine stdout sind jeweils auf 10.000 Zeichen begrenzt. Darueber hinaus schreibt Claude Code den Text in eine Datei im Sitzungsverzeichnis und uebergibt Claude den Pfad samt einer Vorschau von bis zu 2.000 Zeichen. Keine Einstellung oder Umgebungsvariable hebt diese Grenze an. Die Notizen, die ein Hook fuer den Auto-Modus-Klassifikator anfuegt, haben ein eigenes Limit von 2.000 Zeichen pro Tool-Aufruf.

Kann ein Hook sehen, wie viele Token die Sitzung verbraucht hat? Bei den meisten Ereignissen nicht direkt: Das GitHub Issue #11008 forderte Token- und Kostendaten in den Hook-Inputs. Jeder Hook erhaelt transcript_path, und die Usage-Felder des Transkripts koennen summiert werden, mit dem Hinweis, dass die Datei asynchron geschrieben wird und dem letzten Zug hinterherhinken kann. PreModelSwitch ist die Ausnahme: Sein Input enthaelt context_tokens und estimated_cache_write_usd fuer den Wechsel, den er gerade erlauben oder blockieren soll.

Sollten Sie Prompt-basierte Hooks verwenden? Nutzen Sie sie dort, wo eine Abwaegung im Mittelpunkt steht, etwa bei Stop zu pruefen, ob die Aufgabe wirklich erledigt ist. Vermeiden Sie sie bei PreToolUse oder PostToolUse, sofern Sie die Kosten nicht gemessen haben, denn sie fuegen eine Modellanfrage pro Tool-Aufruf hinzu. Alles, was ein reguläer Ausdruck, jq oder ein kurzes Skript entscheiden kann, gehoert in einen Befehls-Hook, der keine Token kostet.

Tokenade ist frei verfuegbar und Open Source.

Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.

Original (Englisch) anzeigen

Posted on Oct 26 • Originally published at tokenade.net

The Claude Code hooks reference now lists 33 events, from SessionStart to ElicitationResult ( Anthropic hooks reference, read 2026-09-24). Most people meet hooks through a list of ten copy-paste examples: a linter after every edit, a desktop ping, a guard on rm -rf. Then they read that a hook cut someone's token usage by 42% or 90% and wonder whether their own hooks help or hurt.

Both happen. A hook that prints to stdout on UserPromptSubmit adds its text to the conversation on every prompt you send. A hook that rewrites a tool call before it runs can stop a 3,000-line log from ever reaching the model.

The difference sits in two details the example lists skip: which event the hook listens to, and which JSON field it returns. This article maps every token-relevant event to what it adds or removes, using only the official reference, and lists the traps that leave a hook silently doing nothing.

A hook is a handler that Claude Code runs at a fixed point of a session: a shell command, an HTTP endpoint, a tool on a connected MCP server, an LLM prompt or a subagent ( reference ). The reference groups the events by cadence. SessionStart and SessionEnd fire once per session. UserPromptSubmit, Stop and StopFailure fire once per turn. PreToolUse and PostToolUse fire on every tool call inside the agentic loop, so their count grows with every file read, search and command the agent runs.

That cadence is the first half of the cost question. A handler that fires once per session and injects 400 characters costs you those characters once, plus their re-reading on each later request. The same 400 characters on PreToolUse land next to every tool result in the session. Anything that enters the conversation is re-sent with the rest of the history on each following model request, which is why prompt caching matters so much for agents: cached re-reads are cheaper than fresh input, but they are never free.

The second half is what the handler returns. A hook can do four things with tokens:

The rest of this article sorts the events into those four buckets. If you have never measured where your session's tokens go, do that first; measuring agent token usage shows how to read the usage fields in the session transcript.

Claude Code's rule for plain stdout is short. On exit 0, "for most events, Claude Code writes stdout to the debug log and doesn't show it in the transcript. The exceptions are UserPromptSubmit, UserPromptExpansion, SessionStart, and PostModelSwitch, where Claude Code adds plain-text stdout as context that Claude can see and act on" ( reference, exit code 0 ).

So an echo in a SessionStart hook is a small, one-time addition. The same echo in a UserPromptSubmit hook is an addition on every prompt. The reference also notes that neither plain stdout nor additionalContext on UserPromptSubmit produces a visible transcript entry: each is injected as a system reminder that starts with the hook's name. You pay for text you never see on screen, which is the most common way a hook setup grows a bill without anyone noticing.

additionalContext is the structured version, and it works on more events. For PreToolUse and PostToolUse it is a "string added to Claude's context alongside the tool result." For SessionStart it is added "at the start of the conversation, before the first prompt." For SubagentStart and PostModelSwitch it is the only thing the hook can do: context, with no blocking ( reference, decision control ). When several hooks return additionalContext for the same event, Claude receives all of the values.

A third path is less obvious. On PostToolUse and PostToolUseFailure, exiting 2 "shows stderr to Claude; the tool already ran." The reference recommends it as the way to surface a warning from those events. Every warning is text in the conversation, so a linter hook that exits 2 with 80 lines of findings on each edit adds 80 lines per edit.

Stop hooks cost differently. Exit 2 on Stop "prevents Claude from stopping, continues the conversation." That is a whole new model turn, with the full history re-sent. A check that fails on every turn keeps the session going until you intervene. The prompt-based variant has a guard for that: when the model returns impossible with ok: false, Claude Code lets the turn end instead of feeding the reason back. Command hooks have no such guard; the loop is yours to bound.

The pattern to keep: context injection is fine when the text is short, changes the model's next move, and fires at the lowest cadence that still does the job. A branch name and the open issue at SessionStart pass that test. A 2,000-character style guide on every UserPromptSubmit belongs in CLAUDE.md, where it is at least part of the cached prefix.

Two fields let a hook shrink what the model reads, and they act at opposite ends of a tool call.

updatedInput on PreToolUse "modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones" ( reference, PreToolUse decision control ). This is where most token-saving hooks work.

A Bash call to npm test can be rewritten to pipe through a filter that keeps failures only; a Read of a 5,000-line file can be narrowed with offset and limit; a Grep without a path can be scoped to src/. The model only ever reads the trimmed result, and nothing extra enters the context. Two details from the reference matter here.

Permission rules are evaluated "against the input your hook returns, not the input Claude sent," so a rewrite cannot smuggle a command past a deny rule. And the replacement is whole-object: forget a field and the tool runs without it.

updatedToolOutput on PostToolUse "replaces the tool's output with the provided value before it is sent to Claude. The value must match the tool's output shape." The reference now recommends it over the older updatedMCPToolOutput, which works on MCP tools only. This is the field to use when you cannot know in advance how large a result will be: let the call run, then keep the error lines of a build log, drop the progress bars, or cut a JSON response down to the keys the model needs. The reference example replaces a Bash result's stdout with a redacted string, which shows the shape.

Note what decision: "block" on PostToolUse does and does not do: it "adds the reason next to the tool result. Claude still sees the original output; to replace it, use updatedToolOutput." A blocking PostToolUse hook meant to hide a huge output adds text and removes nothing.

The published results for this approach are single-team benchmarks, so read them as orders of magnitude. SitePoint reported on 2026-08-20 that Graft, a hook-based framework, cut mean token usage from 8,070 to 4,650 per task on its authors' internal SWE-bench Verified runs, a 42% drop ( SitePoint ). Spotify Engineering published an engineer's account in September 2026 titled "Portal by Spotify cut my Claude Code token usage by 90%," which argues that most of what a coding agent does is I/O, such as reading five files to answer a question about one method ( Spotify Engineering ). Neither is a controlled test on your repository. The mechanism behind both is the one described above, and the general technique has its own page: output filtering for command logs.

Every string a hook injects has a hard limit. The reference: "A hook's additionalContext, systemMessage, and initialUserMessage strings, and its plain stdout, are capped at 10,000 characters." Each JSON field is measured separately, plain stdout is measured whole, and several hooks on one event are each measured on their own ( reference, JSON output ).

Over the limit, Claude Code does not truncate. It "saves the output to a file in the session directory and replaces it with the file path and a preview of up to the first 2,000 characters." Claude can read that file, but Claude Code doesn't ask it to. Large valid Bash results are handled the same way. Unlike the Bash ceiling, the hook cap "has no setting or environment variable to raise it."

If a hook regularly produces more than a few hundred characters, the better design is usually to write the details to a file yourself and inject one line pointing to it.

Prompt-based hooks ( type: "prompt" ) "send the hook input and your prompt to a Claude model, Haiku by default"; the model field overrides it ( reference, prompt-based hooks ). Agent-based hooks go further and run a subagent that can use tools before deciding. Both are billed requests on top of your main session.

That changes the arithmetic for high-cadence events. A prompt hook on Stop runs once per turn, which is usually tolerable. A prompt hook on PreToolUse runs a model request for every tool call, and each one carries the hook input, including the full tool arguments. For a check that a regular expression or jq can do, a command hook costs nothing in tokens and answers in milliseconds.

Use prompt and agent hooks where judgment is the point: deciding whether a task is really finished before letting Claude stop, or whether a diff touches something a rule forbids. Keep them off the per-tool-call path unless you have measured the extra requests. Agentic coding costs follow the same logic: every fresh model context starts with its own overhead.

For a long time, the complaint in GitHub issue #11008, opened in November 2025, held: hooks received no token or cost data, so a budget hook had to parse the transcript itself. PreModelSwitch changes that for one expensive moment. Its input includes context_tokens (the tokens the next request re-sends as its prompt), prompt_cache_warm, cache_ttl, and estimated_cache_write_usd, the estimated cost of writing that context to the new model's cache ( reference, PreModelSwitch input ).

Why it matters: a model switch forfeits a warm prompt cache. The reference's own example shows a /model opus request from a Sonnet 5 session with 182,340 context tokens and an estimated cache write of $1.14 at list price. A hook can return permissionDecision: "ask" with that figure in the reason, so the switch happens with the price in front of you. The default timeout for this event is 30 seconds, and unlike PreToolUse, a PreModelSwitch hook that times out blocks the switch.

For every other event, the transcript remains the source. Each hook receives transcript_path, and the reference warns that the file "is written asynchronously and may lag the in-memory conversation." A cost hook reading it should expect the latest turn to be missing. For per-session numbers without writing a hook, see how to reduce Claude Code token usage, which covers the existing counters.

What are Claude Code hooks? Hooks are handlers Claude Code runs automatically at fixed points of a session: before and after tool calls, when you submit a prompt, when a session starts or ends, before compaction, and about thirty other events. A handler can be a shell command, an HTTP endpoint, an MCP tool, an LLM prompt or a subagent. It receives the event as JSON (on stdin for commands) and can return a decision, extra context, or a rewritten tool input or output. You configure them in settings files or through the /hooks menu.

Do Claude Code hooks use tokens? Only when their output enters the conversation or when they call a model. On most events, stdout from a successful hook goes to the debug log and costs nothing. Plain stdout on UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch is added to context, additionalContext is added wherever it is supported, and stderr from an exit-2 PostToolUse hook is shown to Claude. Prompt and agent hooks are separate model requests, Haiku by default for prompt hooks.

Can a hook reduce Claude Code token usage? Yes, through two fields. updatedInput on PreToolUse rewrites a tool call before it runs, for example narrowing a file read or piping a test command through a filter. updatedToolOutput on PostToolUse replaces a tool's result before Claude reads it. Published results come from single-team benchmarks: SitePoint reported a 42% drop per task for Graft on its authors' own SWE-bench runs in August 2026. Measure on your own sessions before trusting a figure.

What is the difference between PreToolUse and PostToolUse? PreToolUse fires before a tool call executes and can block it, ask for confirmation, or rewrite its arguments with updatedInput. PostToolUse fires after a call succeeds; it cannot undo the call, but it can add context, show a warning via exit 2, or replace the result with updatedToolOutput. For token savings, PreToolUse avoids producing a large output at all, and PostToolUse handles output whose size you cannot predict.

Why does my hook not block anything? The usual causes are exit code 1 instead of 2, a script path that does not exist, a matcher that matches no tool (an MCP prefix without.* ), text printed before the JSON on stdout, or a PreToolUse hook that timed out. The reference is explicit that a timed-out command hook on PreToolUse lets the tool call continue. Enable debug logging to see the parse or validation message, and test each gate with a call it should stop.

Is there a size limit on hook output? Yes. additionalContext, systemMessage, initialUserMessage and plain stdout are each capped at 10,000 characters. Above that, Claude Code writes the text to a file in the session directory and gives Claude the path plus a preview of up to 2,000 characters. No setting or environment variable raises this cap. The notes a hook attaches for the auto mode classifier have a separate 2,000-character cap per tool call.

Can a hook see how many tokens the session has used? Not directly on most events: GitHub issue #11008 asked for token and cost data in hook inputs. Every hook receives transcript_path, and the transcript's usage fields can be summed, keeping in mind that the file is written asynchronously and may lag the latest turn. PreModelSwitch is the exception: its input carries context_tokens and estimated_cache_write_usd for the switch it is about to allow or block.

Should I use prompt-based hooks? Use them where a judgment call is the point, such as checking before Stop whether the task is really done. Avoid them on PreToolUse or PostToolUse unless you have measured the cost, because they add one model request per tool call. Anything a regular expression, jq or a short script can decide belongs in a command hook, which costs no tokens.

Tokenade is free and open source: https://github.com/pi-infected/tokenade

Originaltitel: Claude Code Hooks: Which Ones Add Tokens, Which Cut ThemQuelle: dev.to (dev.to). Original öffnen