# Hook in Claude Code: configurazione e 7 esempi

> 7 hook pronti all’uso per .claude/settings.json, da copiare e incollare: protezione di .env, prettier, force-push su main, trattini lunghi e un server di sviluppo. 4 tipi di handler e 31 eventi.

Source: https://ailoot.ai/blog/it/claude-code-hooks/
Canonical: https://ailoot.ai/blog/it/claude-code-hooks/
Language: it
Publisher: Ailoot (https://ailoot.ai/)
Translation: guide-121
Revision: sha256-5cf85c1cebfd7fe597002d38240955a923513164ccdad5162de5749e3e4ccfcf


Gli hook in Claude Code sono comandi shell, chiamate HTTP o brevi verifiche LLM eseguiti automaticamente prima o dopo un’azione di Claude. Aggiungono controlli su eventi specifici, formattano dopo le modifiche e inseriscono contesto aggiornato all’avvio. Un PreToolUse corrispondente può negare quella chiamata; le regex sono controlli di comodità, non un confine per ogni accesso a file o Git. Di seguito trovi una panoramica dei 4 tipi di handler, dei 31 eventi del ciclo di vita, un test per capire «quando una regola dovrebbe essere un hook», 7 hook pronti da copiare in `.claude/settings.json` e 3 insidie in cui sono incappato personalmente.



[Documentazione di Ailoot: API, chiavi e configurazione](https://ailoot.ai/docs/).

## Cosa sono gli hook in Claude Code?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Un hook è un comando shell, una chiamata HTTP, un breve prompt LLM o un subagente che Claude Code esegue in risposta a uno specifico evento del ciclo di vita. A differenza delle istruzioni in `CLAUDE.md`, che il modello può ignorare o dimenticare, un hook viene eseguito ogni volta allo stesso modo: è codice, non un suggerimento.

</div>

Per sei mesi ho mantenuto in `CLAUDE.md` la regola «non modificare mai direttamente `.env`». Claude la violava una volta al mese. Non intenzionalmente; semplicemente la dimenticava verso la fine di una lunga sessione. Una volta è finito un token in più in `.env`; un’altra, una variabile con un nome duplicato ha sovrascritto quella in uso.

Ho spostato quella regola in un hook, rendendola impossibile da dimenticare. Quando uno strumento di modifica diretta corrispondente fornisce un percorso `.env`, l’hook rifiuta la chiamata. Redirezioni Bash, script, MCP e altri percorsi richiedono controlli separati: il file non diventa immutabile.

<figure>
<blockquote>
<p>Gli hook sono comandi shell, endpoint HTTP o prompt LLM che definisci e che vengono eseguiti automaticamente in punti specifici del ciclo di vita di Claude Code.</p>
</blockquote>
<figcaption>— Anthropic, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

In altre parole, un hook è un tuo comando che Claude Code esegue in occasione di un evento specifico. Ogni modifica a un file, avvio di Bash, inizio di sessione o completamento di un subagente è un evento a cui puoi collegare un hook.

Perché è importante per chi programma con il vibe coding:

- Proteggere da modifiche accidentali i file che contengono segreti.
- Eseguire automaticamente prettier/eslint dopo ogni generazione di codice.
- Bloccare `git push --force` su main senza un’autorizzazione esplicita.
- Inserire contesto aggiornato all’inizio di ogni sessione.
- Registrare le azioni di Claude a fini di audit.

CLAUDE.md gestisce il contesto. Gli hook gestiscono le **azioni**. Sono due cose diverse e confonderle è il principale errore dei principianti.

## In cosa differiscono gli hook da CLAUDE.md e dalle skill?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

`CLAUDE.md` è un testo che il modello legge all’inizio di una sessione e cerca di seguire. Un hook è un comando shell che il processo di Claude Code esegue in risposta a un evento, indipendentemente dal modello. Una skill è un pacchetto di istruzioni con un nome che Claude può invocare con lo strumento Skill, come una funzione. Ognuno dei tre serve a tipi diversi di attività.

</div>

La documentazione di Anthropic spiega esplicitamente la differenza tra `CLAUDE.md` e gli hook:

<figure>
<blockquote>
<p>Il contenuto di CLAUDE.md viene fornito come messaggio dell’utente dopo il prompt di sistema, non come parte del prompt di sistema. Claude lo legge e cerca di seguirlo, ma non è garantita un’adesione rigorosa.</p>
</blockquote>
<figcaption>— Anthropic, documentazione sulla memoria, https://code.claude.com/docs/en/memory</figcaption>
</figure>

In altre parole, il contenuto di `CLAUDE.md` arriva a Claude come primo messaggio dell’utente, non come istruzione di sistema. Il modello lo legge e cerca di seguirlo, ma non ci sono garanzie, soprattutto verso la fine di una lunga sessione, quando il contesto cresce e le istruzioni precedenti vengono espulse.

Un hook funziona in modo diverso. Si trova in `settings.json`, non dipende dal modello e viene eseguito dal processo di Claude Code in occasione di un evento specifico: `PreToolUse`, `PostToolUse`, `SessionStart` e altri. Il comando viene eseguito prima che Claude riceva il risultato e può bloccare fisicamente un’azione (exit 2) o modificare un file dopo averlo editato (exit 0).

Confronto:

| Funzionalità | CLAUDE.md | Hook | Skill |
|----|----|----|----|
| Dove si trova | `CLAUDE.md` nella radice del progetto | Cartella `.claude/settings.json` | Cartella `.claude/skills/` |
| Chi lo esegue | Il modello lo legge come parte di un messaggio dell’utente | Il processo di Claude Code in occasione di un evento | Il modello tramite lo strumento Skill |
| Garanzia di esecuzione | No (best effort) | Un comando configurato e corrispondente esegue codice; guasti e uscita dipendono dall’evento | Tramite una chiamata al modello (con un grant, ma non a livello shell) |
| A cosa serve | Contesto del progetto, tono, stile, instradamento | Restrizioni, formattazione, controlli su file specifici | Procedure complesse composte da più passaggi e con parametri |
| Quando viene eseguito | A ogni messaggio del modello in una sessione | In occasione di un evento del ciclo di vita | Quando viene invocato dal modello |
| Può bloccare un’azione? | No | Sì (exit 2) | No |

Il modello completo di `CLAUDE.md` e 6 regole per un file vivo si trovano nella guida [Come configurare CLAUDE.md nel 2026: un modello pronto all’uso e 6 regole](https://ailoot.ai/blog/it/claude-md-template/). Scopri le skill come capacità con un nome in [Claude Code Skills: come creare la tua libreria](https://ailoot.ai/blog/it/claude-code-skills-library/).

Un hook è il livello più basso: niente modello, niente interpretazione, solo shell.

Gli hook sono uno degli strumenti di [context engineering](https://ailoot.ai/blog/it/context-engineering/), insieme a Plan Mode e /compact.



## 4 tipi di hook: Command, HTTP, Prompt, Agent: quale scegliere?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Claude Code ha 4 tipi di handler. Command è uno script shell, il tipo più comune. HTTP invia una richiesta POST al tuo URL, per team e integrazioni. Prompt esegue una breve chiamata Haiku per un controllo semantico. Agent crea un subagente con accesso agli strumenti per una convalida approfondita. Ai principianti bastano Command e Prompt; coprono il 90% delle attività.

</div>

| Tipo | Cosa fa | Quando sceglierlo | Complessità |
|----|----|----|----|
| **Command** | Esegue un comando shell, riceve JSON su stdin e risponde con un codice di uscita e stdout | Nel 90% dei casi: controllare percorsi, formattare, fare grep sui contenuti, lsof, operazioni git | bassa |
| **HTTP** | Invia una richiesta POST al tuo URL con un corpo JSON e riceve una risposta JSON | Quando una regola deve essere gestita dal team/CI: convalida centralizzata, dashboard di stato condivisa, audit remoto | media (richiede un server) |
| **Prompt** | Invia un breve prompt a Haiku (per impostazione predefinita) e ottiene sì/no per una valutazione semantica | Quando grep non basta: «valuta se questo testo ha un tono aziendale» | bassa |
| **Agent** | Crea un subagente con accesso a Read/Grep/Glob per controlli composti da più passaggi | Convalida impegnativa: «trova tutti i file modificati e controlla che esistano i test corrispondenti» | alta |

L’errore più comune è cercare di usare Command quando serve Prompt. Per esempio, «non usare trattini lunghi nei file `.md`» è un’attività da grep, quindi Command è l’ideale. Ma «non scrivere con un tono aziendale» è una questione semantica e richiede Prompt.

Se hai appena iniziato, usa Command. I 7 hook pronti all’uso qui sotto sono tutti di tipo Command. HTTP/Prompt/Agent verranno dopo, quando sarai pronto.

## Dove si trova settings.json e quali sono i 4 livelli di configurazione?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Claude Code cerca la configurazione in 4 posizioni: enterprise (distribuita tramite MDM), utente (`~/.claude/settings.json`), progetto (`.claude/settings.json` nella radice del repository) e locale (`.claude/settings.local.json`, escluso da git). All’inizio della sessione i 4 file vengono uniti; il livello più basso ha la precedenza.

</div>

| Livello | Percorso | Quando si applica | In git? |
|----|----|----|----|
| Enterprise | `/Library/Application Support/ClaudeCode/settings.json` (Mac), `/etc/claude-code/settings.json` (Linux) | Per tutto il team tramite MDM | no |
| Utente | `~/.claude/settings.json` | Tutti i progetti di un utente | no |
| Progetto | `.claude/settings.json` nella radice del repository | Questo progetto, per tutto il team | **sì** |
| Locale | `.claude/settings.local.json` | Solo questo progetto e questo utente | **no** (in `.gitignore`) |

Tengo i 7 hook descritti qui sotto nel file `.claude/settings.json` del **progetto**, così vengono inclusi automaticamente quando si clona il repository e Claude applica le stesse regole in una nuova finestra. Gli hook sperimentali personali vanno in `.claude/settings.local.json`, così non interferiscono con il team.

Tutti e 4 i file vengono uniti all’inizio della sessione. Se le regole sono in conflitto, prevale il livello più basso (locale sul progetto, progetto sull’utente).

**Il modo più rapido per creare il tuo primo hook** è usare il menu interattivo `/hooks` direttamente in Claude Code. Scegli un evento dall’elenco, seleziona un matcher (per esempio, `Bash` o `Edit`) e incolla un comando. Claude Code scrive JSON valido in `settings.json` per te.

## 31 eventi del ciclo di vita: quali servono ai principianti?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Attualmente Claude Code ha 31 eventi del ciclo di vita, suddivisi in 3 frequenze: una volta per sessione (`SessionStart`/`SessionEnd`), una volta per turno (`UserPromptSubmit`/`Stop`) e a ogni chiamata a uno strumento (`PreToolUse`/`PostToolUse`). Ai principianti servono 4 dei 31 eventi: `PreToolUse` per le restrizioni, `PostToolUse` per le modifiche automatiche, `SessionStart` per il contesto e `Stop` per i controlli finali.

</div>

<figure>
<blockquote>
<p>Gli eventi si verificano con tre frequenze: una volta per sessione (SessionStart, SessionEnd), una volta per turno (UserPromptSubmit, Stop, StopFailure) e a ogni chiamata a uno strumento all’interno del ciclo agentico (PreToolUse, PostToolUse).</p>
</blockquote>
<figcaption>— Anthropic, riferimento agli hook, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

È importante comprendere le tre frequenze per non confondersi:

1.  **Una volta per sessione** - `SessionStart`, `SessionEnd`, `Setup`. Vengono eseguiti una volta quando Claude Code si avvia e si chiude. Usali per inizializzare il contesto e ripulire i file temporanei.
2.  **Una volta per turno** - `UserPromptSubmit`, `UserPromptExpansion`, `Stop`, `StopFailure`. Si attivano per ciascuno dei tuoi messaggi e ogni volta che il modello si ferma. Usali per registrare la conversazione e controllare il risultato finale.
3.  **A ogni chiamata a uno strumento** - `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`, `PostToolBatch`, `SubagentStart`, `SubagentStop`. Si attivano a ogni modifica di file, avvio di Bash, lettura o ricerca. È il livello più intenso, dove si trovano restrizioni, formattazione e convalide.

L’elenco completo dei 31 eventi include eventi asincroni/reattivi: `TeammateIdle`, `PreCompact`, `PostCompact`, `Elicitation`, `ElicitationResult`, `InstructionsLoaded`, `ConfigChange`, `CwdChanged`, `FileChanged`, `WorktreeCreate`, `WorktreeRemove`, `Notification`. Sono destinati a scenari avanzati; per ora puoi ignorarli.

## Test in cinque domande: quando una regola dovrebbe essere un hook e quando dovrebbe restare in CLAUDE.md?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Se una regola riguarda un’azione (non il contesto), è legata alla sicurezza/al denaro/ai dati, si può verificare con grep/regex, è difficile rimediare se viene violata e Claude l’ha già violata almeno una volta, dovrebbe diventare un hook. Se meno di tre risposte su cinque sono «sì», resta in `CLAUDE.md`. Ho applicato il test alle mie 80 righe e 5 regole sono diventate hook.

</div>

Per me funziona come una legge, non come un suggerimento. Ho ridotto la scelta tra `CLAUDE.md` e un hook a un semplice test in cinque domande. Se almeno tre risposte sono «sì», la regola diventa un hook. Se sono meno di tre, resta nel testo.

**Il test «quando una regola dovrebbe essere un hook?»:**

1.  Questa regola riguarda un’azione anziché il contesto? («non eseguire `rm -rf`» è un’azione; «ho un progetto TypeScript» è contesto.)
2.  Questa regola riguarda la sicurezza, il denaro o i dati? («non toccare `.env`», «non pubblicare chiavi».)
3.  Puoi verificarla con grep, regex o il codice di uscita di un comando shell? («dopo aver modificato `.md`, controlla che non ci siano trattini lunghi».)
4.  Se la regola viene violata, le conseguenze sarebbero difficili da annullare? («force-push su main» è difficile da annullare; «aver dimenticato un punto alla fine» è facile.)
5.  Hai già visto Claude dimenticare questa regola almeno una volta?

**Esempio: «Non usare trattini lunghi; usa solo trattini brevi».** È un’azione? Sì. Sicurezza? No. Verificabile con grep? Sì. Difficile da annullare? No. L’hai già vista violata? Sì. **3 su 5: inseriscila in un hook.**

**Controesempio: «Faccio vibe coding, non sviluppo software; scrivi senza slang».** È un’azione? Più simile a un contesto. Sicurezza? No. Verificabile con regex? Troppo difficile. Difficile da annullare? No. L’hai già vista violata? Sì. **1 su 5: lasciala in `CLAUDE.md`.**

Cinque delle mie regole hanno superato il test. Le ho spostate negli hook. Il restante 75% è rimasto nel file di testo.

## 7 hook pronti da copiare

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Qui sotto trovi 7 hook pronti all’uso per `.claude/settings.json`: bloccare le modifiche a `.env`, eseguire prettier automaticamente, impedire il force-push su main, controllare i trattini lunghi, liberare una porta prima di `npm run dev`, eseguire il commit automatico dopo test riusciti e inserire contesto aggiornato all’inizio di una sessione. Copia ciascuno nel file, riavvia Claude Code e funzionerà.

</div>

Tutti e 7 gli hook sono archiviati in un unico file `.claude/settings.json` nella radice del progetto. Ogni voce contiene un tipo di evento, un filtro per lo strumento (matcher) e un comando shell. Exit 2 rifiuta una chiamata PreToolUse corrispondente prima dell’esecuzione. In PostToolUse l’azione è già avvenuta: l’hook fornisce feedback senza annullarla.

<figure>
<blockquote>
<p>Per la maggior parte degli eventi hook, solo il codice di uscita 2 blocca un’azione. Claude Code considera il codice di uscita 1 un errore non bloccante e prosegue con l’azione, anche se in Unix 1 indica convenzionalmente un errore.</p>
</blockquote>
<figcaption>— Anthropic, riferimento agli hook, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

`exit 0` significa successo. `exit 1` significa errore, ma Claude prosegue (è il primo antipattern qui sotto). `exit 2` dipende dall’evento: può bloccare PreToolUse, mentre PostToolUse riferisce soltanto su un’azione già avvenuta.

### Hook 1. Controllare le modifiche dirette a `.env` (PreToolUse, exit 2)

Blocca i tentativi di modificare direttamente qualsiasi file che corrisponda a `.env`, `.env.local`, `.env.production` e così via.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Non modificare direttamente `.env`. Dimmi quale variabile aggiungere e aprirò io il file.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** prima di ogni `Edit`/`Write`/`MultiEdit`, recupera il percorso del file, controlla con grep se corrisponde al pattern `.env` e, in caso affermativo, restituisce exit 2 con un messaggio. Claude vede stderr e mi dice: «Ho provato a modificare `.env`, ma l’hook me l’ha impedito; dimmi quale variabile aggiungere».

### Hook 2. Eseguire prettier automaticamente dopo ogni modifica al codice (PostToolUse, exit 0)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** dopo ogni `Edit`/`Write`, recupera il percorso e, se l’estensione è `.ts`/`.tsx`/`.js`/`.jsx`/`.json`, esegue prettier in background con `&`. Esce sempre con 0 perché si tratta di formattazione, non di una restrizione. Se prettier non riesce, non è grave: il file resta com’era.

La differenza rispetto all’hook 1 è fondamentale: PreToolUse + exit 2 è un blocco rigido **prima** dell’azione. PostToolUse + exit 0 è una modifica leggera **dopo** l’azione. Sono due modalità diverse per attività diverse.

### Hook 3. Bloccare `git push --force` sul branch main (PreToolUse, exit 2)

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Il force-push su main è vietato. Crea un nuovo branch o chiedimi di farlo manualmente.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** a ogni avvio di Bash controlla che il comando contenga `push`, `--force`/`--force-with-lease`/`-f` e menzioni `main`. Se tutte e tre le condizioni corrispondono, blocca l’azione con un messaggio chiaro.

Prima avevo questa regola in maiuscolo in `CLAUDE.md`. Claude la violava comunque circa una volta ogni due settimane quando gli chiedevo di «fare force-push per annullare il mio commit sbagliato». L’hook riconosce le forme letterali mostrate, ma alias, script, branch impliciti o altri client Git possono sfuggire. Proteggi main sul remoto e limita le credenziali disponibili all’agente.

### Hook 4. Bloccare i trattini lunghi nei file di testo (PostToolUse, exit 2)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"Il file $FILE contiene un trattino lungo (U+2014). Sostituiscilo con un trattino normale (U+002D).\" >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** PostToolUse con exit 2 segnala a Claude un controllo fallito dopo la modifica. Non annulla né impedisce la scrittura. Se Claude modifica un file e lascia un trattino lungo, l’hook fallisce, Claude riceve un feedback e ci riprova. Dopo 2–3 sessioni, Claude smette del tutto di inserire questo carattere.

Nota che grep cerca specificamente il carattere del trattino lungo (U+2014) usando `$'\xe2\x80\x94'`, la notazione Bash per scriverlo come byte UTF-8. Se scrivi semplicemente `grep -q '-'`, intercetterà i trattini normali e verrà eseguito su ogni file. Un carattere invisibile manda in errore tutta la logica.

### Hook 5. Liberare la porta 3000 prima di `npm run dev` (PreToolUse, exit 0)

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Porta 3000 liberata prima dell’avvio' >&2; }; exit 0"
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** prima di ogni `npm run dev` (o `pnpm`/`yarn`), termina il processo sulla porta 3000. Esce sempre con 0 perché è una preparazione, non una restrizione. Questo evita una serie di situazioni in cui Claude vede «porta in uso», pensa che ci sia un problema nel codice e inizia a correggere cose, quando invece era semplicemente rimasto in esecuzione il vecchio server.

### Hook 6. Eseguire il commit automatico dopo test riusciti (PostToolUse, exit 0)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
          }
        ]
      }
    ]
  }
}
```

 **Cosa fa:** legge stdin JSON una volta e accetta solo i comandi di test semplici esatti mostrati. Prosegue solo se `tool_response.exit_code` è esplicitamente zero; se la versione non fornisce quel campo, salta il commit senza indovinare. Sostituisci `src/orders.ts` e `tests/orders.test.ts` con i file esatti di competenza del compito. Modifiche già in staging fermano la procedura per non includere lavoro estraneo. Un add fallito la interrompe; un diff vuoto non crea commit; i normali hook restano attivi. Prova offline input riusciti, falliti e senza risultato prima di attivarla.

Antipattern: aggiungere `git commit --no-verify` a ogni Edit. Mantieni i controlli del commit. Se un controllo attiva ricorsivamente l’automazione, restringi il trigger o riprogettalo senza saltare la verifica per impostazione predefinita. Esamina diff in staging e competenza sui file prima dei commit automatici.

### Hook 7. Inserire contesto aggiornato all’inizio della sessione (SessionStart con additionalContext)

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
          }
        ]
      }
    ]
  }
}
```

**Cosa fa:** all’inizio della sessione raccoglie il branch Git corrente e l’elenco delle modifiche non sottoposte a commit, poi restituisce JSON con un campo `additionalContext`. Claude Code inserisce questo testo nel primo messaggio di sistema, così Claude sa fin dall’inizio dove si trova e cosa è rimasto dalla sessione precedente.

<figure>
<blockquote>
<p>Usa additionalContext per le informazioni che Claude dovrebbe conoscere sullo stato corrente dell’ambiente o su un’operazione appena completata: stato dell’ambiente (branch corrente, destinazione del deployment, feature flag attivi), regole condizionali del progetto (quale comando di test si applica al file appena modificato), dati esterni (issue aperte assegnate a te, risultati CI recenti).</p>
</blockquote>
<figcaption>— Anthropic, riferimento agli hook, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

Estensioni di questo hook: recuperare il commit più recente e la sua descrizione, lo stato di GitHub Actions, lo sprint attivo in Linear/Jira e le issue aperte del repository corrente.

## 3 insidie degli hook che colgono una persona su due

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

La prima insidia: un hook con exit 1 passa senza segnalazioni e Claude ignora l’errore. La seconda: il lavoro pesante in un hook viene interrotto dopo un timeout di 60 secondi. La terza: un hook vede solo le variabili d’ambiente di `~/.zshrc`, non quelle del `.env` del progetto. Ognuna si risolve con una riga, ma senza conoscere queste insidie può sembrare che «gli hook non funzionino».

</div>

### Insidia 1. Un hook con exit 1 passa senza segnalazioni

È la più pericolosa. Per una settimana ho pensato che il mio protettore di `.env` funzionasse, ma poi ho scoperto che nell’hook avevo `jq -r '.tool_input.file_path'` senza un controllo per null. Quando `tool_input` era vuoto, jq usciva con codice 1. Claude interpretava exit 1 come «errore dell’hook, continua», quindi l’azione veniva eseguita. Non capivo perché `.env` venisse comunque toccato. Ho individuato la causa solo quando ho eseguito manualmente l’hook in un terminale con input vuoto.

**La soluzione è una sola regola:** inizia ogni hook controllando null e termina con `|| exit 0`. Qualsiasi uscita imprevista produrrà così un’uscita «sicura» anziché passare senza segnalazioni.

```bash
# Modello per un inizio e una fine sicuri:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... la tua logica ...
exit 0   # oppure || exit 0 su una sola riga
```

### Insidia 2. Gli hook lenti vengono interrotti per timeout

Avevo un hook che eseguiva `eslint --fix` sull’intero file dopo Edit. I file grandi (1000+ righe) richiedevano 8–10 secondi per essere elaborati. Il timeout predefinito degli hook di Claude Code è 60 secondi, quindi pensavo di rientrare nei limiti. Ma quando avvengono diverse modifiche in un minuto, gli hook si accumulano e si mettono in coda, e alcuni vengono semplicemente saltati. Me ne sono accorto solo quando nel log ho visto «hook timed out».

**Soluzione:** se un hook richiede un lavoro pesante, eseguilo in background. `eslint --fix "$FILE" &` senza aspettare il risultato. Così l’hook restituisce exit 0 in pochi millisecondi mentre il lavoro effettivo prosegue in background.

```bash
# Operazione pesante in background:
npx eslint --fix "$FILE" &
exit 0
```

### Insidia 3. Un hook non può vedere le variabili d’ambiente del progetto

Pensavo che `process.env` fosse disponibile in un hook come in un’app Node.js. Non è così. L’hook viene eseguito in una shell avviata dal processo genitore di Claude Code e vede solo ciò che si trova in `~/.zshrc` o `~/.bashrc`, oltre a ciò che Claude Code passa esplicitamente. Se l’hook dipende da `OPENAI_API_KEY` contenuta in `.env`, dovrai leggere `.env` manualmente.

**Soluzione:** leggi esplicitamente `.env` nel comando dell’hook.

```bash
# Passare l’ambiente del progetto all’hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Oppure con dotenv:
dotenv -e .env -- node my-validator.js
```

È particolarmente problematico in un monorepo, dove pacchetti diversi hanno i propri file `.env`. Risolvi passando `PWD` e avviando esplicitamente il comando con `cd "$PWD"`.

## Come si testano gli hook prima del commit?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Un hook è un comando shell. Puoi eseguirlo manualmente in un terminale con uno stdin fittizio, controllarne il codice di uscita e stderr e fare tutto questo prima che venga eseguito da Claude Code. In questo modo risparmi ore di debug ed eviti hook che bloccano la propria operatività.

</div>

**Scenario di test di base.** Prendi il comando dell’hook dal JSON, apri un terminale e passagli il JSON che Claude Code invierebbe:

```bash
# Verifica hook 1 (proteggi .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
  echo "BLOCKED" >&2
  exit 2
} || exit 0
'
echo "Exit code: $?"
```

Dovresti vedere `BLOCKED` in stderr e `Exit code: 2`. Se ottieni exit 0, la tua regex non funziona; correggi grep.

**Scenari da controllare:**

1.  **Percorso positivo** - il file dovrebbe essere bloccato. `.env` per l’hook 1, `git push --force main` per l’hook 3.
2.  **Percorso negativo** - il file non dovrebbe essere bloccato. `README.md` per l’hook 1, `git push origin feature-branch` per l’hook 3.
3.  **Casi limite** - input vuoto, valori null, caratteri imprevisti. L’hook dovrebbe restituire exit 0 o fallire con un errore chiaro, non passare senza segnalazioni.

**Dove mettere gli hook sperimentali.** Non in `.claude/settings.json`, ma in `.claude/settings.local.json` (non in git). Inserisci lì i nuovi hook sotto `PostToolUse` usando `echo` al posto dell’azione reale, controlla che si attivino e poi spostali in `settings.json` di produzione.

```json
// Esperimento in settings.local.json: scrive in /tmp/hook-debug.log
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
          }
        ]
      }
    ]
  }
}
```

Dopo 5–10 modifiche in Claude Code, apri `/tmp/hook-debug.log` per vedere quali file hanno attivato l’hook. Se è vuoto, il matcher non corrisponde; correggi la regex.

## E adesso?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Punto chiave">

Punto chiave

Inizia con hook PostToolUse che restituiscono exit 0: sono i più sicuri e non bloccano nulla. Dopo una settimana, aggiungi il tuo primo hook PreToolUse con exit 2 per proteggere `.env` o impedire il force-push. Dopo due settimane, `CLAUDE.md` passerà da un contratto di 200 righe a una breve descrizione del contesto di 50–80 righe, con controlli di eventi selezionati negli hook e limiti rigidi separati tramite permessi, sandbox e protezioni remote.

</div>

5 passaggi per la prossima settimana:

1.  **Cerca con grep nel tuo `CLAUDE.md`** parole come «mai», «vietato», «sempre» e «deve». Ognuna di queste regole può diventare un hook. Se una regola è imperativa, il file di testo non la farà rispettare.
2.  **Inizia con PostToolUse + exit 0.** Sono i più sicuri: apportano una modifica senza bloccare nulla. Prettier (hook 2), terminare il server di sviluppo (hook 5), commit automatico dopo i test (hook 6).
3.  **Solo quando ti senti sicuro, aggiungi il tuo primo PreToolUse + exit 2.** Per esempio, proteggi `.env` (hook 1) o impedisci il force-push (hook 3). È un blocco rigido e un errore nel comando grep può bloccare anche te.
4.  **Testa gli hook manualmente prima del commit** (vedi la sezione qui sopra). Ti risparmierà ore di debug.
5.  **Tieni `settings.local.json` per gli esperimenti.** Inserisci lì i nuovi hook usando echo al posto dell’azione reale.

Una volta configurati gli hook, il prossimo passaggio in questo cluster è [come annullare le modifiche in Claude Code](https://ailoot.ai/blog/it/claude-code-undo-changes/), nel caso in cui un hook lasci passare qualcosa di pericoloso. Se vuoi che Claude ricordi la struttura del tuo progetto tra una sessione e l’altra, crea un [Second Brain in Claude Code](https://ailoot.ai/blog/it/claude-code-second-brain/) (cartella `business/` + knowledge base collegata). E per dare un nome alle skill del tuo progetto così da poterle riutilizzare, consulta [Claude Code Skills](https://ailoot.ai/blog/it/claude-code-skills-library/).

<div class="section" role="doc-bibliography" aria-label="Fonti">

### Fonti

- [Riferimento agli hook di Claude Code - documentazione ufficiale Anthropic](https://code.claude.com/docs/en/hooks)
- [Documentazione sulla memoria di Claude Code - informazioni su CLAUDE.md e sul prompt di sistema](https://code.claude.com/docs/en/memory)
- [Automatizzare i flussi di lavoro con gli hook - guida ufficiale Anthropic con esempi](https://code.claude.com/docs/en/hooks-guide)

</div>





Se ti serve accedere alle reti neurali tramite Ailoot, dai un’occhiata ad [Ailoot](https://ailoot.ai/).

## Domande frequenti

### Perché un hook deve leggere il JSON di input una sola volta?

Stdin è un flusso: il primo jq lo consuma e le letture successive trovano EOF. Salva l’input una volta, poi estrai comando e risultato da quel valore. Un esito assente o sconosciuto resta non verificato: salta il commit automatico.

### Le regex degli esempi creano un confine di sicurezza?

No. Controllano eventi specifici e stringhe riconoscibili. Redirezioni shell, alias, script, MCP o altri percorsi possono eluderle: per limiti forzati usa credenziali ristrette, permessi sui file, sandbox e branch protetti.


## Ailoot · L’azienda e il suo portafoglio di progetti

Ailoot si basa su un’esperienza operativa consolidata, un’ampia base di clienti, un servizio stabile e un controllo costante dei KPI.

L’azienda e il suo portafoglio di progetti: milioni di clienti; vendite nell’ordine dei miliardi ogni mese.

Ailoot mette questa esperienza operativa al servizio dell’accesso API alle funzioni AI nei prodotti dei clienti, negli agenti, nelle applicazioni e nelle automazioni.
