
Hook PreToolUse odmawia odczytu plików powyżej progu liczby linii i odsyła agenta do skilla, który każe streścić plik tańszemu modelowi. Na pliku liczącym 8 861 linii i 305 215 bajtów kontekst głównego agenta przyjmuje 1 595 bajtów zamiast 305 215, czyli stosunek 191 do 1. U Spotify ten sam układ obniża o około 90% tokeny odczytu monorepo w Javie.
Odczyt pliku wygląda niewinnie, dopóki się go nie policzy: plik wp-includes/post.php z instalacji WordPressa waży 8 861 linii i 305 215 bajtów, czyli rzędu 87 000 tokenów, które trafiają do kontekstu i zostają w nim aż do kolejnego /clear. Spotify opublikował 3 września 2026 wpis wyjaśniający, jak ich zespoły obniżyły ten wydatek o około 90%, a mechanizm da się odtworzyć bez ich platformy.
Co dokładnie zmierzył Spotify
Portal by Spotify to komercyjna dystrybucja Backstage. Wpis, podpisany przez Dimitriego Mazmanova, opisuje w nim tryby: deklaratywne agenty działające na efemerycznym runtime, z własnym modelem i własnymi podłączonymi narzędziami MCP. Dwa tryby dają większość zysku: bulk-reader, który odpowiada na pytanie, czytając kilka plików, i code-writer, który generuje powtarzalny kod na podstawie istniejących wzorców. Przykłady z artykułu uruchamiają je na Gemini 2.5 Flash.
Po stronie Claude Code za ten objazd odpowiada plugin shunt z publicznego repozytorium spotify/portal-ai-plugins (Apache-2.0). Jego plik dokumentacji opisuje trzy warstwy: hooki PreToolUse, czyli check-file-size i check-bash-read, które blokują odczyty powyżej 350 linii (próg regulowany zmienną środowiskową SHUNT_MIN_LINES), skrypty bash wywołujące CLI Portal, oraz skille Markdown, które wyjaśniają agentowi, kiedy z nich korzystać. Zapowiadana oszczędność sięga około 90% przy masowym odczycie monorepo w Javie.
Liczba, która najbardziej zaskakuje, jest gdzie indziej we wpisie: jedna czwarta ankietowanych szefów inżynierii miałaby już wydawać 200 do 500 dolarów na dewelopera miesięcznie na tokeny, a niektórzy ponad 2 000. Na Hacker News wątek otwarty 4 września (269 punktów i 173 komentarze, gdy go czytałem, 7 września) nie kwestionuje samej zasady, tylko jej zakres: jeden z komentujących zgłasza, że tani model przepuścił subtelny błąd współbieżności, kilku innych przypomina, że główny agent i tak często sam odczytuje pliki na nowo, co znosi zysk. Zasada, która wraca najczęściej, mieści się w jednym zdaniu: mały model ma prawo wskazywać, nie decydować.
Dlaczego odczyt pliku kosztuje tak dużo?
Wersja 2.1.257 Claude Code, wydana 1 września 2026, uczyniła z Claude Fable 5.1 model domyślny: milion tokenów kontekstu, 10 $ za milion tokenów wejściowych, 50 $ za wyjściowe, 0,25 $ za milion przy odczycie z cache’u. Wróćmy do pliku z początku.
| Operacja na post.php | Tokeny | Koszt, stawka Fable 5.1 |
|---|---|---|
Pełny odczyt przez Read | ≈ 87 000 wejściowych | 0,87 $ |
| Ta sama treść odczytana ponownie z cache’u | ≈ 87 000 z cache’u | 0,02 $ |
| Odpowiedź modelu roboczego (1 595 bajtów) | ≈ 456 wejściowych | 0,005 $ |
Tych 87 000 tokenów nie płaci się raz. Wracają przy każdej kolejnej turze. Dopóki cache trzyma, wracają za 0,02 $ za turę, gdy tylko wystygnie, rachunek wraca do 0,87 $. Dokumentacja Anthropic mówi to wprost: pytanie w jednej linii zadane w sesji otwartej od rana rozlicza się z użycia całej rozmowy. Podaje też rzędy wielkości obserwowane w firmach, około 13 $ na dewelopera za dzień aktywności, 150 do 250 $ miesięcznie, przy czym 90% użytkowników mieści się poniżej 30 $ dziennie.
Napisanie hooka PreToolUse
Hook PreToolUse otrzymuje na wejściu standardowym JSON wywołania narzędzia, tool_name, tool_input, cwd, permission_mode, i zwraca decyzję na wyjściu standardowym. Trzy warianty: kod wyjścia 0 z {} zostawia decyzję zwykłemu przepływowi uprawnień, kod 0 z obiektem hookSpecificOutput rozstrzyga sprawę, kod 2 blokuje bez wyjątku, z komunikatem pobranym ze stderr. Dla tego układu potrzebna jest druga forma: odmówić i wyjaśnić, dokąd pójść.
Hook deklaruje się w settings.json projektu, więc można go wersjonować razem z repozytorium:
{
"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…"
}
]
}
]
}
}A oto skrypt, w wersji, którą uruchomiłem:
#!/usr/bin/env bash
# bulk-read-guard.sh: hook PreToolUse.
# Wejście: JSON hooka na stdin. Wyjście: JSON na stdout, kod wyjścia 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')
# Odczyt już ograniczony poniżej progu kosztuje prawie nic.
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 i tail -20 nadal są tanie.
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 0Liczą się trzy szczegóły. matcher obejmuje Read i Bash, bez tego agent obchodzi odmowę przez cat. Odczyt już ograniczony przez limit poniżej progu przechodzi bez dyskusji, inaczej hook odsyła agenta do skilla w kółko. A permissionDecisionReason jest adresowany do agenta, nie do Ciebie, to tekst, który on czyta, żeby zdecydować, co dalej, więc musi podawać dokładną ścieżkę skryptu do uruchomienia. Oto, co hook zwraca przy odmowie:
{
"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é."
}Skill za hookiem
Hook potrafi powiedzieć nie, nie potrafi pracować. Zajmuje się tym skill, a tylko jego metadane, nazwa i opis, zajmują kontekst, dopóki nie zostanie wywołany, podczas gdy te same wskazówki umieszczone w CLAUDE.md ładują się przy każdej sesji. Format jest zgodny ze standardem opisanym w naszym przewodniku po Agent Skills: folder, SKILL.md, obok niego skrypty.
---
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.Skrypt wywoływany przez skill mieści się w kilkunastu liniach. Komenda modelu roboczego przechodzi przez zmienną, co pozwala ją podmienić podczas testów:
#!/usr/bin/env bash
# resume-fichier.sh: wysyła cały plik do taniego modelu i zwraca tylko odpowiedź.
# Plik nigdy nie trafia do kontekstu głównego agenta.
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}"
# W produkcji: claude -p --model haiku. Podczas testu: 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_CMDCo pokazały testy
Hook testuje się bez agenta: czyta JSON ze standardowego wejścia i zwraca decyzję na wyjściu. Napisałem więc stanowisko testowe, które buduje ładunki opisane w dokumentacji hooków, wysyła je do skryptu i porównuje zwróconą decyzję z oczekiwaną. Dziesięć przypadków, dziesięć zgodności:
$ 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=0Przypadek 09 jest najbardziej pouczający: sed -n '1,4000p' przechodzi. Hook zna tylko cat, head i tail, a każda inna komenda odczytu go omija. Hook obniża średni wydatek, nie zamyka drzwi.
Pozostaje część modelowa. Podpiąłem zmienną SHUNT_CMD pod codex exec na GPT-6 Astra, tego z porównania Astra kontra Fable 5.1, który nie jest tanim modelem, ale mierzy całą instalację od początku do końca:
$ 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 # naliczone modelowi roboczemu
$ wc -lc < resultats/resume-codex.txt
10 1595 # co wraca do głównego agentaTrzydzieści sześć sekund, 91 805 tokenów naliczonych modelowi roboczemu, i 1 595 bajtów, które wracają do głównego agenta: stosunek 191 do 1 w tym, co przyjmuje kontekst. Odpowiedź została sprawdzona ręcznie, przez grep: sześć wywołań current_user_can(), linie 3436, 3489, 4734, 4736, 5105 i 7705, wszystkie sześć numerów się zgadza. Z deterministycznym zamiennikiem, zwykłym grep po deklaracjach zamiast modelu, ten sam układ zwraca 3 460 bajtów w 0,56 sekundy: gdy pytanie ma charakter strukturalny, rola robotnika nie zawsze wymaga modelu.
Ile naprawdę kosztuje Claude Code?
Przy API wszystko widać w tabeli przypomnianej wyżej. Przy abonamencie pytanie dotyczy limitów. 31 sierpnia 2026 promocja „+50%” na limity tygodniowe się skończyła, zastąpiona trwałym podniesieniem podstawowego limitu o 25%, co dla korzystających z promocji oznacza około 17% mniej niż wcześniej, i tego samego dnia zajęło wątek na Hacker News. W obu przypadkach punktem wyjścia jest komenda /usage: pokazuje koszt sesji, podział na modele, a od wersji 2.1.251 linię „Prompt cache”, która podaje udział tokenów wejściowych obsłużonych przez cache i liczbę chybień. Przy abonamencie dodaje też udział zużycia przypisany skillom, subagentom, pluginom i każdemu serwerowi MCP.
Dźwignie do uruchomienia, zanim cokolwiek zainstalujesz
- Cache promptu. Trzyma godzinę na abonamencie, pięć minut na kredytach użycia albo kluczu API. Jeśli linia „Prompt cache” w
/usagepokazuje chybienia, szukaj przyczyny przed jakąkolwiek inną optymalizacją: definicja narzędzia, która zmienia się w trakcie sesji, wystarczy, żeby przepisać wszystko od nowa. - Subagenty. Rozwlekłe wyjście z zestawu testów czy dziennika zostaje w ich kontekście, do góry wraca tylko podsumowanie. Dla prostych zadań dokumentacja zaleca
model: haikuw konfiguracji subagenta: to wbudowana wersja pomysłu Spotify, bez platformy do instalowania. - Porządki w skillach. Skill załadowany, ale nigdy niewywołany, i tak płaci swoimi metadanymi przy każdej sesji. Komenda
/skill-doctor, dostępna od wersji 2.1.261 z 4 września, wypisuje dokładnie takie przypadki i ich koszt w kontekście, poświęcamy jej osobny artykuł.
Spotify zresztą niczego tu nie wynalazł. Dokumentacja kosztów podaje własny przykład hooka filtrującego, PreToolUse, który przepisuje komendę testu, żeby zwracała tylko błędy, używając pola updatedInput zamiast odmowy. Przepisanie zamiast odmowy jest zwykle łagodniejsze, agent nie traci swojej tury.
Co warto zapamiętać
- Układ mieści się w dwóch plikach: hook
PreToolUse, który odmawia powyżej progu linii, i skill, który przenosi pracę gdzie indziej. - Odmowa musi iść przez
permissionDecision: "deny"ipermissionDecisionReason, który wskazuje skrypt zastępczy, nie przez kod wyjścia 2. - Na pliku liczącym 8 861 linii główny agent przyjmuje 1 595 bajtów zamiast 305 215, czyli 191 do 1.
- Obejmij
Bashtak samo jakRead, i przepuszczaj odczyty już ograniczone przezlimit. - Zanim cokolwiek zainstalujesz:
/usage, cache promptu, subagenty na tanim modelu.
Częste błędy
Read i zapomnieć o Bash Agent obchodzi odmowę przez cat. Matcher musi obejmować Read|Bash, a skrypt sprawdzać .tool_input.command.Read z limit poniżej progu kosztuje prawie nic. Bez tego testu hook odsyła agenta do skilla w kółko i płacisz za objazd na darmo.permissionDecision: deny i permissionDecisionReason, który wskazuje skrypt.sed -n '1,4000p' przechodzi, jak każda nieprzewidziana komenda odczytu. Hook obniża średnią, niczego nie gwarantuje..env, zrzuty baz i wszystko, co zawiera dane uwierzytelniające.

