Due livelli che non fanno lo stesso lavoro

CLAUDE.md e settings.json finiscono spesso nella stessa frase e non sono la stessa cosa: «Settings rules are enforced by the client regardless of what Claude decides to do. CLAUDE.md instructions shape Claude’s behavior but are not a hard enforcement layer.» Se stai scrivendo in CLAUDE.md una regola che deve valere sempre, sei nel file sbagliato.

I file CLAUDE.md si caricano da quattro posizioni: la policy gestita dall’organizzazione, ~/.claude/CLAUDE.md per le tue preferenze su tutti i progetti, ./CLAUDE.md oppure ./.claude/CLAUDE.md per il progetto condiviso in git, e ./CLAUDE.local.md per il progetto ma solo tuo, da mettere in .gitignore. Non si sovrascrivono: vengono concatenati dalla radice del filesystem verso la working directory, quindi le istruzioni più vicine al punto di lancio sono le ultime lette. La policy gestita non si può escludere.

Sulla dimensione: punta a meno di 200 righe per file, perché file più lunghi consumano contesto e riducono l’aderenza. E attenzione a una scorciatoia che non funziona: spezzare il file con gli import @percorso aiuta l’organizzazione ma non riduce il contesto, perché gli importati si caricano comunque al lancio. Quello che riduce davvero sono le regole con ambito di percorso in .claude/rules/, con un frontmatter paths di glob: entrano in contesto solo quando Claude lavora su file corrispondenti.

La precedenza di settings.json è il fatto operativo

Cinque livelli, dal più alto al più basso: managed settings, argomenti da riga di comando (incluso --settings), project local .claude/settings.local.json, progetto condiviso .claude/settings.json, utente ~/.claude/settings.json. Chiave per chiave, il livello più alto vince.

Due dettagli cambiano il modo di scrivere questi file. Primo: le liste si fondono fra i livelli invece di sostituirsi, quindi un progetto può aggiungere permessi ai tuoi senza cancellarli — con eccezioni come fallbackModel, dove l’ordine ha significato e il valore viene preso intero dal file di precedenza più alta. Secondo: sono JSON stretto, quindi un commento // o una virgola finale sono un errore di sintassi e la sessione riparte segnalando un errore di settings.

Le variabili d’ambiente non sono un sesto livello di questa pila. Si decide coppia per coppia: ANTHROPIC_MODEL esportata nella shell si applica sopra la chiave model di qualunque file, mentre ANTHROPIC_DEFAULT_MODEL vale solo quando nessun file imposta model. Confonderle è il motivo più frequente per cui «la configurazione non prende».

Pinning del modello: il nome senza data non è un alias

Qui c’è la nozione da correggere, e la documentazione la nomina proprio come tale: «A common misconception is that dateless model IDs such as claude-sonnet-4-6 behave as evergreen pointers that route to the latest or best-performing version. That is not the case.»

Dalla generazione 4.6 in poi l’ID senza data è lo snapshot. Anthropic non aggiorna pesi o configurazione di un ID esistente: una versione nuova esce con un ID nuovo. Gli alias veri esistono solo per i modelli precedenti, dove per esempio claude-sonnet-4-5 punta allo snapshot datato più recente di quella minor. Quindi «pinnare il modello» non è una scelta che devi fare sull’API: ci sei già dentro. La scelta vera è la scadenza. Ogni ID ha il proprio calendario di deprecazione e ritiro, le richieste a un modello ritirato falliscono, e il preavviso è di almeno 60 giorni per i modelli rilasciati pubblicamente. Tratta quindi l’ID come una variabile deployabile, non come una costante nel codice.

Una sfumatura che evita indagini inutili: i pesi sono fissi, ma l’infrastruttura attorno al modello — router, classificatori, logica di campionamento — cambia, e può produrre differenze osservabili a ID invariato.

In Claude Code il modello si risolve con un ordine suo: --model o ANTHROPIC_MODEL, poi la chiave model nei file di settings, poi ANTHROPIC_DEFAULT_MODEL, poi il default dell’organizzazione. Gli alias di famiglia opus, sonnet e haiku sono comodi in interattivo e sbagliati in una pipeline: puntano all’ultimo modello della famiglia, quindi la CI cambia modello senza che nessuno abbia fatto un commit. Lato amministratore il vincolo è availableModels, reso stringente da enforceAvailableModels.

Versionare i prompt e le dipendenze fra plugin

Il prompt è codice sorgente: su file nel repository, che sia una skill in .claude/skills/ o il modulo che costruisce il system prompt, dentro la stessa code review e con lo stesso storico. Ma il diff da solo non dice se la modifica è un miglioramento: serve un set di casi. La guida sui test è esplicita sul compromesso da accettare: «More questions with slightly lower signal automated grading is better than fewer questions with high-quality human hand-graded evals.» Ne segue una regola di rilascio: un cambio di prompt e un cambio di ID modello sono due deploy separati, altrimenti quando il numero si muove non sai chi l’ha mosso.

Le dipendenze fra plugin si dichiarano nell’array dependencies di .claude-plugin/plugin.json, come stringa nuda o come oggetto con vincolo di versione:

{
  "name": "deploy-kit",
  "version": "3.1.0",
  "dependencies": [
    "audit-logger",
    { "name": "secrets-vault", "version": "~2.1.0" }
  ]
}

Senza version la dipendenza segue l’ultima versione disponibile, e un rilascio a monte la cambia sotto il tuo plugin senza avviso. Con il vincolo, la risoluzione avviene sui tag git della repository che ospita la dipendenza, nella forma secrets-vault--v2.1.0, che crei con claude plugin tag --push. Quando più plugin vincolano la stessa dipendenza i range si intersecano; se nessuna versione li soddisfa tutti, l’installazione fallisce con range-conflict. Le dipendenze verso un altro marketplace sono bloccate di default e si abilitano solo con allowCrossMarketplaceDependenciesOn nel marketplace radice.