
Ein PreToolUse-Hook lehnt Dateilesungen jenseits eines Zeilenschwellenwerts ab und schickt den Agenten zu einem Skill, der die Datei von einem günstigeren Modell zusammenfassen lässt. Bei einer Datei mit 8.861 Zeilen und 305.215 Byte erhält der Kontext des Hauptagenten 1.595 Byte statt 305.215, ein Verhältnis von 191 zu 1. Bei Spotify senkt dasselbe Setup die Lese-Tokens eines Java-Monorepos um rund 90 %.
Eine Dateilese wirkt harmlos, bis man sie zählt: Die wp-includes/post.php einer WordPress-Installation bringt es auf 8.861 Zeilen und 305.215 Byte, in der Größenordnung von 87.000 Tokens, die in den Kontext wandern und dort bis zum nächsten /clear bleiben. Spotify hat am 3. September 2026 einen Beitrag veröffentlicht, der erklärt, wie die eigenen Teams diese Ausgabe um rund 90 % gesenkt haben, und der Mechanismus lässt sich auch ohne ihre Plattform nachbauen.
Was Spotify tatsächlich gemessen hat
Portal by Spotify ist die kommerzielle Distribution von Backstage. Der Beitrag, verfasst von Dimitri Mazmanov, beschreibt darin Modes: deklarative Agenten, die auf einer kurzlebigen Laufzeitumgebung laufen, mit eigenem Modell und eigenen angebundenen MCP-Tools. Zwei Modes tragen den Großteil des Gewinns: bulk-reader, der eine Frage beantwortet, indem er mehrere Dateien liest, und code-writer, der repetitiven Code aus bestehenden Mustern erzeugt. Die Beispiele im Artikel lassen sie auf Gemini 2.5 Flash laufen.
Auf der Claude-Code-Seite erzwingt das shunt-Plugin aus dem öffentlichen Repository spotify/portal-ai-plugins (Apache-2.0) den Umweg. Seine Dokumentationsdatei beschreibt drei Schichten: PreToolUse-Hooks, nämlich check-file-size und check-bash-read, die Lesevorgänge über 350 Zeilen hinaus blockieren, ein Schwellenwert, der über die Umgebungsvariable SHUNT_MIN_LINES einstellbar ist, Bash-Skripte, die die Portal-CLI aufrufen, und Markdown-Skills, die dem Agenten erklären, wann er sie einsetzen soll. Die angekündigte Ersparnis liegt bei rund 90 % beim Massenlesen eines Java-Monorepos.
Die Zahl, die wehtut, steht an anderer Stelle im Beitrag: Ein Viertel der befragten Engineering-Verantwortlichen gebe bereits 200 bis 500 Dollar pro Entwickler und Monat für Tokens aus, manche über 2.000. Auf Hacker News stellt der am 4. September eröffnete Thread (269 Punkte und 173 Kommentare, als ich ihn am 7. gelesen habe) nicht das Prinzip infrage, sondern seinen Geltungsbereich: Ein Kommentator berichtet, dass ein günstiges Modell einen subtilen Concurrency-Fehler durchgelassen hat, mehrere weisen darauf hin, dass der Hauptagent die Dateien am Ende oft selbst noch einmal liest, was den Gewinn zunichtemacht. Die Regel, die am häufigsten genannt wird, passt in einen Satz: Das kleine Modell darf zeigen, nicht entscheiden.
Warum kostet eine Dateilese so viel?
Version 2.1.257 von Claude Code, veröffentlicht am 1. September 2026, hat Claude Fable 5.1 zum Standardmodell gemacht: ein Kontext von einer Million Tokens, 10 $ pro Million Eingabe-Tokens, 50 $ für Ausgabe, 0,25 $ pro Million bei Cache-Lesevorgängen. Zurück zur Datei vom Anfang.
| Vorgang auf post.php | Tokens | Kosten, Tarif Fable 5.1 |
|---|---|---|
Vollständige Lesung durch Read | ≈ 87.000 Eingabe | 0,87 $ |
| Derselbe Inhalt erneut aus dem Cache | ≈ 87.000 Cache-Lesevorgang | 0,02 $ |
| Antwort eines Arbeitsmodells (1.595 Byte) | ≈ 456 Eingabe | 0,005 $ |
Diese 87.000 Tokens werden nicht einmal bezahlt. Sie werden bei jeder folgenden Runde erneut mitgeschickt. Solange der Cache hält, kosten sie 0,02 $ pro Runde, sobald er kalt ist, springt die Rechnung zurück auf 0,87 $. Die Dokumentation von Anthropic sagt es unverblümt: Eine einzeilige Frage in einer seit dem Morgen offenen Sitzung zieht die Nutzung des gesamten Gesprächs nach sich. Sie nennt auch die in Unternehmen beobachteten Größenordnungen: rund 13 $ pro Entwickler und aktivem Tag, 150 bis 250 $ im Monat, wobei 90 % der Nutzer unter 30 $ pro Tag bleiben.
Den PreToolUse-Hook schreiben
Ein PreToolUse-Hook erhält auf seiner Standardeingabe das JSON des Tool-Aufrufs, tool_name, tool_input, cwd, permission_mode, und gibt seine Entscheidung auf der Standardausgabe zurück. Drei mögliche Ausgänge: Ein Exit-Code 0 mit {} überlässt die Entscheidung dem gewohnten Berechtigungsfluss, ein Code 0 mit einem hookSpecificOutput-Objekt entscheidet, ein Code 2 blockiert in jedem Fall, mit der Meldung aus stderr. Für dieses Setup braucht es die zweite Form: ablehnen und erklären, wohin stattdessen.
Der Hook wird in der settings.json des Projekts deklariert, lässt sich also mit dem Repository versionieren:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/bulk-read-guard.sh",
"timeout": 10,
"statusMessage": "Contrôle de la taille du fichier…"
}
]
}
]
}
}Und hier das Skript, in der Version, die ich ausgeführt habe:
#!/usr/bin/env bash
# bulk-read-guard.sh — PreToolUse-Hook.
# Eingabe: das JSON des Hooks auf stdin. Ausgabe: JSON auf stdout, Exit-Code 0.
set -uo pipefail
set -f
SEUIL="${SHUNT_MIN_LINES:-350}"
laisser_passer() { echo '{}'; exit 0; }
est_entier() { case "$1" in ''|*[!0-9]*) return 1 ;; *) return 0 ;; esac; }
charge=$(cat)
outil=$(printf '%s' "$charge" | jq -r '.tool_name // empty')
fichier=""
case "$outil" in
Read)
fichier=$(printf '%s' "$charge" | jq -r '.tool_input.file_path // empty')
limite=$(printf '%s' "$charge" | jq -r '.tool_input.limit // empty')
# Eine bereits begrenzte Lesung unter dem Schwellenwert kostet fast nichts.
if est_entier "$limite" && [ "$limite" -le "$SEUIL" ]; then laisser_passer; fi
;;
Bash)
commande=$(printf '%s' "$charge" | jq -r '.tool_input.command // empty')
printf '%s' "$commande" | grep -Eq '^[[:space:]]*(cat|head|tail)([[:space:]]|$)' || laisser_passer
# head -n 40 und tail -20 bleiben günstig.
borne=$(printf '%s' "$commande" \
| sed -nE "s/.*-n[[:space:]]*([0-9]+).*/\1/p;s/.*[[:space:]]-([0-9]+).*/\1/p" | head -1)
if est_entier "$borne" && [ "$borne" -le "$SEUIL" ]; then laisser_passer; fi
for jeton in $commande; do
case "$jeton" in -*) continue ;; esac
if [ -f "$jeton" ]; then fichier="$jeton"; break; fi
done
;;
*) laisser_passer ;;
esac
[ -n "$fichier" ] && [ -f "$fichier" ] || laisser_passer
lignes=$(wc -l < "$fichier" | tr -d ' ')
[ "$lignes" -gt "$SEUIL" ] || laisser_passer
jq -n --arg f "$fichier" --arg l "$lignes" --arg s "$SEUIL" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ($f + " fait " + $l + " lignes, au-dessus du seuil de " + $s
+ ". Passez par le skill bulk-read : "
+ ".claude/skills/bulk-read/scripts/resume-fichier.sh " + $f + " \"votre question\"."
+ " Lecture directe autorisée uniquement avec un limit sous le seuil.")
},
systemMessage: ("bulk-read : " + $f + " (" + $l + " lignes) dévié vers le modèle bon marché.")
}'
exit 0Drei Details zählen. Der matcher deckt Read und Bash ab, sonst umgeht der Agent das Verbot mit einem cat. Eine Lesung, die schon durch ein limit unter dem Schwellenwert begrenzt ist, kommt ohne Weiteres durch, sonst schickt der Hook den Agenten in einer Schleife zurück zum Skill. Und permissionDecisionReason richtet sich an den Agenten, nicht an dich, es ist der Text, den er liest, um die nächsten Schritte zu entscheiden, er muss also den genauen Pfad des auszuführenden Skripts nennen. So sieht die Antwort des Hooks bei einer Ablehnung aus:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "wp-includes/post.php fait 8861 lignes, au-dessus du seuil de 350. Passez par le skill bulk-read : .claude/skills/bulk-read/scripts/resume-fichier.sh wp-includes/post.php \"votre question\". Lecture directe autorisée uniquement avec un limit sous le seuil."
},
"systemMessage": "bulk-read : wp-includes/post.php (8861 lignes) dévié vers le modèle bon marché."
}Der Skill hinter dem Hook
Der Hook kann Nein sagen, arbeiten kann er nicht. Das übernimmt der Skill, und nur seine Metadaten, Name und Beschreibung, belegen den Kontext, solange er nicht ausgelöst wird, während dieselben Anweisungen in einer CLAUDE.md bei jeder Sitzung geladen würden. Das Format folgt dem Standard aus unserem Leitfaden zu Agent Skills: ein Ordner, eine SKILL.md, Skripte daneben.
---
name: bulk-read
description: Lire, inventorier ou résumer un fichier de plus de 350 lignes sans le charger dans le contexte. À utiliser dès qu'un hook refuse une lecture pour cause de taille, avant d'ouvrir un gros fichier PHP, un journal d'erreurs ou un dump SQL.
---
# Lecture déportée vers un modèle bon marché
Le hook `bulk-read-guard.sh` refuse les lectures intégrales au-delà de 350 lignes.
Ne contournez pas le refus avec `cat` : la même règle s'applique à Bash.
## Marche à suivre
1. Formulez une question précise. « Où la validation du panier est-elle faite ? » coûte
moins cher que « résume ce fichier ».
2. Lancez le script :
```
.claude/skills/bulk-read/scripts/resume-fichier.sh <chemin> "<question>"
```
3. Le script envoie le fichier entier au modèle bon marché et ne renvoie que sa réponse,
avec des numéros de ligne.
4. Relisez ensuite les seules zones utiles, avec `Read` et un `limit` sous le seuil :
```
Read(file_path: "<chemin>", offset: 412, limit: 60)
```
## Quand ne pas s'en servir
- Fichier de moins de 350 lignes : lisez-le directement.
- Refactorisation qui doit modifier le fichier : il faut le texte exact, pas un résumé.
- Fichier contenant des secrets : le script l'envoie à un autre modèle.Das vom Skill aufgerufene Skript kommt mit rund einem Dutzend Zeilen aus. Der Befehl des Arbeitsmodells läuft über eine Variable, sodass er sich beim Testen austauschen lässt:
#!/usr/bin/env bash
# resume-fichier.sh — schickt eine ganze Datei an ein günstiges Modell und gibt nur die Antwort zurück.
# Die Datei durchläuft nie den Kontext des Hauptagenten.
set -uo pipefail
fichier="${1:?usage : resume-fichier.sh <chemin> [question]}"
question="${2:-Inventaire des classes, fonctions et points d entree, avec numeros de ligne}"
# In Produktion: claude -p --model haiku. Beim Testen: SHUNT_CMD=./faux-modele.sh
: "${SHUNT_CMD:=claude -p --model haiku}"
{
printf 'Question : %s\n' "$question"
printf 'Reponds en 40 lignes maximum, en citant les numeros de ligne. Ne recopie pas le fichier.\n\n'
printf -- '--- %s ---\n' "$fichier"
cat -- "$fichier"
} | $SHUNT_CMDWas der Testaufbau ergeben hat
Ein Hook lässt sich ohne Agent testen: Er liest JSON von der Standardeingabe und gibt seine Entscheidung auf der Ausgabe zurück. Ich habe also einen Prüfstand geschrieben, der die in der Hook-Dokumentation beschriebenen Nutzlasten baut, sie an das Skript schickt und die zurückgegebene Entscheidung mit der erwarteten vergleicht. Zehn Fälle, zehn Treffer:
$ bash test-hook.sh
Seuil : 350 lignes
Cible : wp-includes/post.php (8861 lignes)
OK 01-read-gros-fichier decision=deny code=0
OK 02-read-limit-120 decision=allow code=0
OK 03-read-limit-2000 decision=deny code=0
OK 04-read-petit-fichier decision=allow code=0
OK 05-bash-cat-gros decision=deny code=0
OK 06-bash-head-40 decision=allow code=0
OK 07-bash-grep decision=allow code=0
OK 08-read-fichier-absent decision=allow code=0
OK 09-bash-sed-plage decision=allow code=0
OK 10-edit-ignore decision=allow code=0Fall 09 ist der lehrreichste: sed -n '1,4000p' kommt durch. Der Hook kennt nur cat, head und tail, und jeder andere Lesebefehl schlüpft daran vorbei. Ein Hook senkt eine durchschnittliche Ausgabe, er schließt keine Tür.
Bleibt die Modellseite. Ich habe die Variable SHUNT_CMD an codex exec auf GPT-6 Astra gehängt, dem Modell aus unserem Vergleich Astra gegen Fable 5.1, das kein günstiges Modell ist, aber die Verkabelung von Anfang bis Ende misst:
$ SHUNT_CMD="codex exec --skip-git-repo-check --sandbox read-only -m gpt-6-astra -" \
./.claude/skills/bulk-read/scripts/resume-fichier.sh wp-includes/post.php \
"Ou est faite la verification des capacites (current_user_can) dans ce fichier ?"
code=0 duree=36s
tokens used 91 805 # dem Arbeitsmodell berechnet
$ wc -lc < resultats/resume-codex.txt
10 1595 # was beim Hauptagenten ankommtSechsunddreißig Sekunden, 91.805 Tokens, die dem Arbeitsmodell berechnet werden, und 1.595 Byte, die beim Hauptagenten ankommen: ein Verhältnis von 191 zu 1 bei dem, was der Kontext schluckt. Die Antwort wurde von Hand per grep geprüft: sechs Aufrufe von current_user_can(), Zeilen 3436, 3489, 4734, 4736, 5105 und 7705, alle sechs Zahlen stimmen. Mit einem deterministischen Ersatz, einem einfachen grep nach den Deklarationen statt des Modells, liefert derselbe Aufbau 3.460 Byte in 0,56 Sekunden: Wenn die Frage strukturell ist, muss der Arbeiter nicht immer ein Modell sein.
Was kostet Claude Code eigentlich?
Bei der API steht alles in der weiter oben genannten Tabelle. Beim Abo wird es zur Frage der Limits. Am 31. August 2026 endete die „+50 %“-Aktion auf die wöchentlichen Limits, ersetzt durch eine dauerhafte Anhebung des Basislimits um 25 %, was für alle, die von der Aktion profitiert hatten, rund 17 % weniger als zuvor bedeutet, was noch am selben Tag einen Hacker-News-Thread füllte. In beiden Fällen ist der Befehl /usage der Ausgangspunkt: Er zeigt die Kosten der Sitzung, die Aufteilung nach Modell und seit Version 2.1.251 eine Zeile „Prompt cache“, die den Anteil der aus dem Cache bedienten Eingabe-Tokens und die Zahl der Fehltreffer angibt. Bei einem Abo ergänzt er den Verbrauchsanteil, der Skills, Subagenten, Plugins und jedem MCP-Server zugeordnet ist.
Die Hebel, bevor du irgendetwas installierst
- Der Prompt-Cache. Er hält eine Stunde bei einem Abo, fünf Minuten bei Nutzungsguthaben oder einem API-Schlüssel. Meldet die Zeile „Prompt cache“ in
/usageFehltreffer, suche die Ursache vor jeder anderen Optimierung: Eine Tool-Definition, die sich mitten in der Sitzung ändert, reicht, um alles neu zu schreiben. - Die Subagenten. Die ausführliche Ausgabe einer Testsuite oder eines Logs bleibt in ihrem eigenen Kontext, nur die Zusammenfassung geht nach oben. Für einfache Aufgaben empfiehlt die Dokumentation
model: haikuin der Konfiguration des Subagenten: Das ist die eingebaute Version von Spotifys Idee, ohne Plattform zum Installieren. - Aufräumen bei den Skills. Ein geladener, aber nie aufgerufener Skill bezahlt trotzdem bei jeder Sitzung seine Metadaten. Der Befehl
/skill-doctor, mit Version 2.1.261 vom 4. September gekommen, listet genau diese und ihre Kontextkosten auf, wir widmen ihm einen ganzen Artikel.
Spotify hat an diesem Punkt im Übrigen nichts erfunden. Die Kosten-Dokumentation liefert ihr eigenes Beispiel für einen Filter-Hook, einen PreToolUse, der den Testbefehl umschreibt, sodass nur die Fehlschläge zurückkommen, unter Verwendung des Felds updatedInput statt einer Ablehnung. Umschreiben statt ablehnen ist oft die sanftere Lösung, der Agent verliert nicht seine Runde.
Was du dir merken solltest
- Das Setup besteht aus zwei Dateien: einem
PreToolUse-Hook, der ab einem Zeilenschwellenwert ablehnt, und einem Skill, der die Arbeit anderswo erledigen lässt. - Die Ablehnung muss über
permissionDecision: "deny"und einenpermissionDecisionReasonlaufen, der das Ersatzskript nennt, nicht über einen Exit-Code 2. - Bei einer Datei mit 8.861 Zeilen erhält der Hauptagent 1.595 Byte statt 305.215, also 191 zu 1.
- Deckt
Bashgenauso ab wieRead, und lasst Lesevorgänge durch, die bereits durch einlimitbegrenzt sind. - Bevor du irgendetwas installierst:
/usage, der Prompt-Cache, Subagenten mit einem günstigen Modell.
Häufige Fehler
Read blockieren und Bash vergessen Der Agent umgeht das Verbot mit cat. Der matcher muss Read|Bash abdecken, und das Skript muss .tool_input.command prüfen.Read mit einem limit unter dem Schwellenwert kostet fast nichts. Ohne diese Prüfung schickt der Hook den Agenten in einer Schleife zum Skill zurück, und du bezahlst den Umweg umsonst.permissionDecision: deny und einem permissionDecisionReason, der das Skript nennt.sed -n '1,4000p' kommt durch, wie jeder nicht vorgesehene Lesebefehl. Ein Hook senkt einen Durchschnitt, er garantiert nichts..env-Dateien, Dumps und alles mit Zugangsdaten aus.

