Prezzo di Claude Code: dividere il conto con hook e skill

Spotify annuncia il 90% di token di lettura in meno grazie a un hook PreToolUse e a degli skill. Ho riprodotto il montaggio senza la loro piattaforma, su un file PHP di 8.861 righe.

Prezzo di Claude Code: dividere il conto con hook e skill
Risposta rapida

Un hook PreToolUse rifiuta le letture di file oltre una soglia di righe e rimanda l'agente a uno skill che fa riassumere il file da un modello più economico. Su un file di 8.861 righe e 305.215 byte, il contesto dell'agente principale incassa 1.595 byte invece di 305.215, ossia un rapporto di 191 a 1. In Spotify, lo stesso montaggio fa scendere di circa il 90% i token di lettura di un monorepo Java.

Una lettura di file non sembra nulla finché non la conti: il wp-includes/post.php di un’installazione WordPress pesa 8.861 righe e 305.215 byte, cioè circa 87.000 token che entrano nel contesto e ci restano fino al prossimo /clear. Spotify ha pubblicato il 3 settembre 2026 un articolo che spiega come i suoi team abbiano fatto scendere questa spesa di circa il 90%, e il meccanismo si riproduce senza la loro piattaforma.

Cosa ha misurato davvero Spotify

Portal by Spotify è la distribuzione commerciale di Backstage. L’articolo, firmato da Dimitri Mazmanov, vi descrive dei modes: agenti dichiarativi che girano su un runtime effimero, ciascuno con un proprio modello e propri strumenti MCP collegati. Due modes concentrano gran parte del guadagno: bulk-reader, che risponde a una domanda leggendo più file, e code-writer, che genera codice ripetitivo a partire da pattern esistenti. Gli esempi dell’articolo li fanno girare su Gemini 2.5 Flash.

Sul versante Claude Code, è il plugin shunt del repository pubblico spotify/portal-ai-plugins (Apache-2.0) a imporre la deviazione. Il suo file di documentazione descrive tre livelli: hook PreToolUse, check-file-size e check-bash-read, che bloccano le letture oltre le 350 righe, soglia regolabile tramite la variabile d’ambiente SHUNT_MIN_LINES, script bash che chiamano la CLI Portal, skill Markdown che spiegano all’agente quando usarli. Il risparmio annunciato si aggira intorno al 90% sulla lettura di massa di un monorepo Java.

Il dato che punge sta altrove nell’articolo: un quarto dei responsabili di ingegneria intervistati spenderebbe già tra 200 e 500 dollari per sviluppatore al mese in token, alcuni oltre 2.000. Su Hacker News, il thread aperto il 4 settembre (269 punti e 173 commenti quando l’ho letto, il 7) non contesta il principio ma il suo perimetro: un commentatore racconta che un modello economico ha lasciato passare un difetto di concorrenza sottile, diversi ricordano che l’agente principale finisce spesso per rileggere i file lui stesso, il che annulla il guadagno. La regola che ricorre più spesso sta in una frase: il modello piccolo ha il diritto di indicare, non di decidere.

Perché una lettura di file costa così tanto?

La versione 2.1.257 di Claude Code, pubblicata il 1° settembre 2026, ha reso Claude Fable 5.1 il modello predefinito: contesto da un milione di token, 10 $ per milione di token in ingresso, 50 $ in uscita, 0,25 $ al milione in lettura dalla cache. Riprendiamo il file iniziale.

Operazione su post.php Token Costo, tariffa Fable 5.1
Lettura integrale con Read ≈ 87.000 in ingresso 0,87 $
Lo stesso contenuto riletto dalla cache ≈ 87.000 in lettura dalla cache 0,02 $
Risposta di un modello operaio (1.595 byte) ≈ 456 in ingresso 0,005 $

Questi 87.000 token non si pagano una volta sola. Vengono rimandati a ogni turno successivo. Finché la cache tiene, ricadono a 0,02 $ a turno, appena si raffredda, il conto torna a 0,87 $. La documentazione di Anthropic lo dice senza giri di parole: una domanda di una riga posta in una sessione aperta dalla mattina trascina l’uso per tutta la conversazione. Fornisce anche gli ordini di grandezza osservati in azienda, circa 13 $ per sviluppatore al giorno attivo, 150-250 $ al mese, con il 90% degli utenti sotto i 30 $ al giorno.

Scrivere l’hook PreToolUse

Un hook PreToolUse riceve sul suo standard input il JSON della chiamata allo strumento, tool_name, tool_input, cwd, permission_mode, e restituisce la sua decisione sullo standard output. Tre esiti: un codice di uscita 0 con {} lascia decidere il flusso di permessi abituale, un codice 0 con un oggetto hookSpecificOutput decide, un codice 2 blocca comunque, con il messaggio preso da stderr. Per questo montaggio serve la seconda forma: rifiutare e spiegare dove andare.

L’hook si dichiara nel settings.json del progetto, quindi versionabile insieme al repository:

json
{
  "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…"
          }
        ]
      }
    ]
  }
}

Ed ecco lo script, nella versione che ho eseguito:

bash
#!/usr/bin/env bash
# bulk-read-guard.sh: hook PreToolUse.
# Input: il JSON dell'hook su stdin. Output: JSON su stdout, codice di uscita 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')
    # Una lettura già limitata sotto la soglia non costa quasi nulla.
    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 e tail -20 restano economici.
    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 0

Contano tre dettagli. Il matcher copre Read e Bash, altrimenti l’agente aggira il rifiuto con un cat. Una lettura già limitata da un limit sotto la soglia passa senza discussioni, altrimenti l’hook rimanda l’agente allo skill in loop. E il permissionDecisionReason si rivolge all’agente, non a te, è il testo che legge per decidere il seguito, quindi deve indicare il percorso esatto dello script da lanciare. Ecco cosa restituisce l’hook in caso di rifiuto:

json
{
  "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é."
}

Lo skill dietro l’hook

L’hook sa dire di no, non sa lavorare. Se ne occupa lo skill, e solo i suoi metadati, nome e descrizione, occupano il contesto finché non viene attivato, laddove le stesse istruzioni messe in un CLAUDE.md vengono caricate a ogni sessione. Il formato è quello dello standard descritto nella nostra guida agli Agent Skills: una cartella, uno SKILL.md, degli script accanto.

markdown
---
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.

Lo script chiamato dallo skill sta in una dozzina di righe. Il comando del modello operaio passa da una variabile, il che permette di sostituirlo durante i test:

bash
#!/usr/bin/env bash
# resume-fichier.sh: invia un file intero a un modello economico e restituisce solo la risposta.
# Il file non attraversa mai il contesto dell'agente principale.
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 produzione: claude -p --model haiku. Durante il test: 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_CMD

Cosa ha dato il banco di prova

Un hook si testa senza agente: legge JSON sul suo standard input e restituisce la sua decisione sull’output. Ho quindi scritto un banco di prova che fabbrica i payload descritti nella documentazione degli hook, li invia allo script e confronta la decisione restituita con quella attesa. Dieci casi, dieci conformità:

bash
$ 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=0

Il caso 09 è il più istruttivo: sed -n '1,4000p' passa. L’hook conosce solo cat, head e tail, e qualsiasi altro comando di lettura lo attraversa. Un hook riduce una spesa media, non chiude una porta.

Resta la parte del modello. Ho collegato la variabile SHUNT_CMD a codex exec su GPT-6 Astra, quello del confronto tra Astra e Fable 5.1, che non è un modello economico, ma misura l’idraulica da un capo all’altro:

bash
$ 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          # fatturati al modello operaio
$ wc -lc < resultats/resume-codex.txt
      10    1595            # ciò che torna all'agente principale

Trentasei secondi, 91.805 token fatturati al modello operaio, e 1.595 byte che tornano all’agente principale: un rapporto di 191 a 1 su ciò che il contesto incassa. La risposta è stata verificata a mano, con grep: sei chiamate a current_user_can(), righe 3436, 3489, 4734, 4736, 5105 e 7705, i sei numeri sono esatti. Con un sostituto deterministico, un semplice grep delle dichiarazioni al posto del modello, lo stesso montaggio restituisce 3.460 byte in 0,56 secondi: quando la domanda è strutturale, l’operaio non ha sempre bisogno di essere un modello.

Quanto costa Claude Code, di preciso?

Con l’API, tutto si legge nella griglia richiamata più sopra. Con l’abbonamento, la questione diventa quella dei limiti. Il 31 agosto 2026 la promozione «+50%» sui limiti settimanali è terminata, sostituita da un aumento permanente del 25% del limite di base, ovvero, per chi approfittava della promozione, circa il 17% in meno rispetto a prima, il che ha occupato un thread di Hacker News lo stesso giorno. In entrambi i casi, il comando /usage è il punto di partenza: mostra il costo della sessione, la ripartizione per modello, e dalla versione 2.1.251 una riga «Prompt cache» che indica la quota di token in ingresso servita dalla cache e il numero di cache miss. Su un abbonamento, aggiunge la quota di consumo attribuita agli skill, ai subagenti, ai plugin e a ogni server MCP.

Le leve da attivare prima di installare qualsiasi cosa

  • La cache di prompt. Dura un’ora con abbonamento, cinque minuti con crediti a consumo o una chiave API. Se la riga «Prompt cache» di /usage segnala cache miss, cerca la causa prima di ogni altra ottimizzazione: una definizione di strumento che cambia a metà sessione basta a riscrivere tutto.
  • I subagenti. L’output verboso di una suite di test o di un log resta nel loro contesto, solo il riepilogo risale. Per i compiti semplici, la documentazione consiglia model: haiku nella configurazione del subagente: è la versione integrata dell’idea di Spotify, senza piattaforma da installare.
  • Il riordino degli skill. Uno skill caricato ma mai invocato paga comunque i suoi metadati a ogni sessione. Il comando /skill-doctor, arrivato con la versione 2.1.261 del 4 settembre, elenca esattamente questi casi e il loro costo in contesto, gli dedichiamo un articolo intero.

Spotify, del resto, non ha inventato nulla su questo punto. La documentazione sui costi offre un proprio esempio di hook di filtraggio, un PreToolUse che riscrive il comando di test per far risalire solo i fallimenti, usando il campo updatedInput invece di un rifiuto. Riscrivere piuttosto che rifiutare è spesso più delicato, l’agente non perde il turno.

Cosa ricordare

  • Il montaggio sta in due file: un hook PreToolUse che rifiuta oltre una soglia di righe, uno skill che fa fare il lavoro altrove.
  • Il rifiuto deve passare per permissionDecision: "deny" e un permissionDecisionReason che indichi lo script alternativo, non per un codice di uscita 2.
  • Su un file di 8.861 righe, l’agente principale incassa 1.595 byte invece di 305.215, ossia 191 a 1.
  • Copri Bash tanto quanto Read, e lascia passare le letture già limitate da un limit.
  • Prima di installare qualsiasi cosa: /usage, la cache di prompt, i subagenti con un modello economico.

Errori frequenti

Bloccare Read e dimenticare Bash L'agente aggira il rifiuto con cat. Il matcher deve coprire Read|Bash e lo script ispezionare .tool_input.command.
Rifiutare una lettura già limitata Un Read con un limit sotto la soglia non costa quasi nulla. Senza questo controllo, l'hook rimanda l'agente allo skill in loop e paghi la deviazione per niente.
Uscire con codice 2 per bloccare Il codice 2 blocca comunque e il messaggio viene da stderr. Per dire all'agente cosa fare invece, esci con codice 0, permissionDecision: deny e un permissionDecisionReason che indichi lo script.
Credere che l'hook chiuda la porta sed -n '1,4000p' passa, come qualsiasi comando di lettura non previsto. Un hook fa scendere una media, non garantisce nulla.
Far riassumere un file sensibile Lo script invia il file intero a un altro modello, spesso presso un altro fornitore. Escludi i file .env, i dump e tutto ciò che porta credenziali.

Claude CodeMCPPerformanceSkills

Damien Flandrin Sviluppatore web dal 2010, creatore di Gekkode e di Email Impact. Ogni articolo è testato su un progetto reale prima della pubblicazione. Contatti
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.