12 · CLI scripting
Finora hai sempre conversato con Claude. Questo modulo lo trasforma in un ingranaggio: un comando che parte, fa una cosa, restituisce il risultato ed esce — senza nessuno alla tastiera. Si chiama modalità headless (“senza testa”, cioè senza interfaccia) o non interattiva, e apre una categoria nuova di usi: il controllo automatico su ogni modifica prima del salvataggio, il report che si genera da solo ogni mattina, la stessa correzione applicata a duecento file in serie. Tutta la sezione Automazione del corso poggia su questo modulo.
Se parti da zeroI tre attrezzi di questo modulo: stdin, stdout, exit code
Ogni comando del terminale ha un ingresso (stdin), un’uscita
(stdout) e un canale per gli errori (stderr); la pipe | collega l’uscita di un
comando all’ingresso del successivo. E quando finisce, ogni comando lascia un
exit code: 0 = tutto bene, altro = problema. Claude, in questa modalità, è
un comando come gli altri: gli si danno dati in pancia con la pipe, e si controlla com’è andata
leggendo l’exit code. Se questi tre concetti sono nuovi, Le basi li mostra
in pratica.
Obiettivi
Sezione intitolata “Obiettivi”- Eseguire Claude Code in modo non interattivo e usarne l’output in uno script.
- Sapere cosa carica
claude -pdi default, e perché in CI serve--bare. - Gestire i permessi in un contesto dove nessuno può cliccare “approva”.
- Continuare e riprendere conversazioni tra invocazioni separate.
- Far ramificare uno script sull’esito di Claude, invece che sperare.
Concetti chiave
Sezione intitolata “Concetti chiave”-p trasforma Claude Code in un comando Unix. Aggiungi -p (o --print) a qualsiasi invocazione e
Claude esegue il task, stampa il risultato ed esce. Legge da stdin, scrive su stdout, restituisce un exit
code: si comporta come grep o jq.
claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash"Exit code 0 in caso di successo, diverso da zero in caso di fallimento — così gli script possono ramificare. Un flag invalido viene riportato su stderr prima che il run parta; un fallimento dentro il run, tipo autenticazione mancante, viene stampato come risultato su stdout.
Di default -p carica tutto quello che caricherebbe una sessione interattiva: hook, skill, plugin,
server MCP, l’auto memory del modulo 4, CLAUDE.md. In locale è quello che vuoi. In CI è un problema: un hook nel
~/.claude di un collega o un server MCP nel .mcp.json del progetto cambiano il risultato da macchina a
macchina.
--bare è la risposta. Salta l’auto-discovery di hook, skill, plugin, server MCP, auto memory e
CLAUDE.md. Stesso risultato su ogni macchina.
In bare mode Claude ha accesso ai tool Bash, lettura file e modifica file. Il contesto va passato a mano:
| Per caricare | Usa |
|---|---|
| Aggiunte al system prompt | --append-system-prompt, --append-system-prompt-file |
| Impostazioni | --settings <file-o-json> |
| Server MCP | --mcp-config <file-o-json> |
| Subagent custom | --agents <json> |
| Un plugin | --plugin-dir <path>, --plugin-url <url> |
I flag che userai davvero
Sezione intitolata “I flag che userai davvero”Un flag è un’opzione del comando, riconoscibile dai trattini. Questi dieci coprono la quasi totalità degli script reali — impara questi, e consulta il resto quando serve:
| Flag | Cosa fa |
|---|---|
-p, --print |
Modalità non interattiva: esegui, stampa, esci |
--bare |
Modalità minima per la CI: nessun caricamento automatico |
--output-format |
Formato della risposta: text (default), json, stream-json — il modulo 13 è tutto su questo |
--json-schema |
Risposta validata contro uno schema: dati, non prosa |
--allowedTools |
Tool pre-approvati, che eseguono senza chiedere |
--permission-mode |
La modalità di permesso di partenza (modulo 6) |
--continue, -c |
Continua la conversazione più recente |
--resume, -r |
Riprende una sessione per ID o nome |
--max-turns |
Tetto ai turni: la cintura di sicurezza dei run non sorvegliati |
--append-system-prompt |
Aggiunge istruzioni di ruolo (“sei un revisore di sicurezza…”) |
Il riferimento completo dei flag, per quando serve
Input/output
| Flag | Cosa fa |
|---|---|
--input-format |
text o stream-json |
--verbose |
Output dettagliato — necessario con stream-json |
--include-partial-messages |
Include gli eventi di streaming parziale |
Permessi
| Flag | Cosa fa |
|---|---|
--disallowedTools |
Regole di divieto |
--tools |
Limita quali tool integrati Claude può usare |
--permission-prompt-tool |
Tool MCP che gestisce le richieste di permesso al posto tuo |
--dangerously-skip-permissions |
Salta ogni conferma — solo in ambienti isolati |
Sessioni
| Flag | Cosa fa |
|---|---|
--session-id |
Usa un ID di sessione specifico (UUID valido) |
--fork-session |
Riprendendo, crea un nuovo ID invece di riusare l’originale |
--no-session-persistence |
Non salvare la trascrizione (solo con -p) |
--name, -n |
Nome visualizzato della sessione |
System prompt e contesto
| Flag | Cosa fa |
|---|---|
--append-system-prompt-file |
Come --append-system-prompt, ma da file |
--system-prompt / --system-prompt-file |
Sostituisce l’intero system prompt |
--add-dir |
Directory di lavoro aggiuntive |
--setting-sources |
Quali sorgenti di settings caricare: user, project, local |
--settings |
File JSON di settings, o JSON inline |
Limiti, modello, diagnostica
| Flag | Cosa fa |
|---|---|
--max-budget-usd |
Tetto di spesa (per chi usa una API key a consumo) |
--model / --fallback-model |
Modello per la sessione, con eventuale ripiego |
--effort |
Quanto il modello “ci pensa”: da low a max |
--debug / --debug-file <path> |
Log di debug, eventualmente su file |
--safe-mode |
Tutte le personalizzazioni disattivate, per diagnosticare |
--strict-mcp-config |
Solo i server passati con --mcp-config, ignora il resto |
I permessi negli script
Sezione intitolata “I permessi negli script”In una sessione -p non c’è nessuno che possa cliccare “approva”. Tre strategie, dalla più permissiva
alla più blindata:
-
Elencare i tool — l’approccio granulare:
Terminal window claude -p "Run the test suite and fix any failures" \--allowedTools "Bash,Read,Edit" -
Usare la sintassi delle regole di permesso — molto più preciso:
Terminal window claude -p "Look at my staged changes and create an appropriate commit" \--allowedTools "Bash(git diff *),Bash(log *),Bash(git status *),Bash(git commit *)" -
Impostare una modalità di base. Due modalità servono ai due estremi:
acceptEditsper i task locali in cui le modifiche ai file vanno bene senza conferma:Terminal window claude -p "Apply the lint fixes" --permission-mode acceptEdits(= “applica le correzioni del linter” — le modifiche ai file passano, gli altri comandi chiedono comunque)
dontAskper la CI blindata: nega tutto ciò che non è già nelle tue regolepermissions.allowo nell’insieme dei comandi di sola lettura:Terminal window claude -p "Run the checks" --permission-mode dontAskNota per i casi limite: alcuni tool restano negati in
dontAskanche con una regola di allow — quelli che per natura richiedono un umano, comeAskUserQuestion(fa domande all’utente) e i tool marcati come interattivi dal loro server MCP.
Pipe e redirezione
Sezione intitolata “Pipe e redirezione”La modalità non interattiva legge da stdin:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txtUn esempio che si usa davvero, come script di package.json:
{ "scripts": { "lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\"" }}Fare la pipe della diff invece di lasciarla leggere a Claude ha un vantaggio concreto: Claude non ha bisogno del permesso Bash per ottenerla.
Continuare le conversazioni
Sezione intitolata “Continuare le conversazioni”# Prima richiestaclaude -p "Review this codebase for performance issues"
# Continua la più recenteclaude -p "Now focus on the database queries" --continueclaude -p "Generate a summary of all issues found" --continueCon più conversazioni in parallelo, cattura l’ID:
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')claude -p "Continue that review" --resume "$session_id"I due comandi possono girare da directory diverse: Claude Code trova la sessione per ID in qualsiasi progetto sulla macchina.
Skill e comandi in -p
Sezione intitolata “Skill e comandi in -p”Le skill e i comandi custom invocabili dall’utente funzionano in modalità -p: metti /nome-skill
nella stringa del prompt e Claude Code lo espande prima di eseguire.
claude -p "/pre-commit"I comandi built-in che esistono solo nell’interfaccia terminale, come /login, non sono disponibili.
/model, /effort, /fast, /color e /rename accettano il valore come argomento — /model sonnet —
e /mcp senza argomenti stampa un riepilogo testuale dello stato dei server. Per cambiare
un’impostazione da un’invocazione -p, passa chiave=valore a /config, per esempio
/config thinking=false.
Esempio guidato
Sezione intitolata “Esempio guidato”-
La forma più semplice.
Terminal window cd ~/progetti/il-tuo-progettoclaude -p "What does the auth module do?"Terminal window cd $env:USERPROFILE\progetti\il-tuo-progettoclaude -p "What does the auth module do?" -
Controlla l’exit code. È la base di ogni script serio.
Terminal window claude -p "What does the auth module do?" > /dev/nullecho "exit code: $?"Terminal window claude -p "What does the auth module do?" | Out-NullWrite-Host "exit code: $LASTEXITCODE" -
Pipe di dati. Un caso reale: spiegare un errore di build.
Terminal window npm run build 2>&1 | tail -50 | claude -p \'concisely explain the root cause of this build error. one paragraph, no preamble.' -
Un linter di typo sulla diff. Nota che a Claude non serve nessun permesso: la diff arriva via stdin.
Terminal window git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else." -
Un task che modifica file. Qui i permessi servono:
Terminal window claude -p "Fix all ESLint errors in src/" \--allowedTools "Read,Edit,Bash(npx eslint *)" \--max-turns 20--max-turnsè la cintura di sicurezza: senza, un task che non converge gira finché non finisci i token. -
Un revisore di sicurezza riusabile. Salvalo come
review.sh:review.sh #!/usr/bin/env bashset -euo pipefail# gh è la CLI ufficiale di GitHub (si installa a parte: cli.github.com);# "gh pr diff" scarica la diff della PR indicata come primo argomentogh pr diff "$1" | claude -p \--append-system-prompt "You are a security engineer. Review for vulnerabilities. Report only findings that have a concrete exploit path; ignore style." \--output-format jsonTerminal window chmod +x review.sh./review.sh 123"$1"è il primo argomento:bash review.sh 123scarica la diff della PR 123. -
Una catena di invocazioni che condividono contesto.
audit.sh #!/usr/bin/env bashset -euo pipefailsid=$(claude -p "Map the error handling patterns in src/. Don't propose changes yet." \--output-format json | jq -r '.session_id')echo "sessione: $sid"claude -p "Now list the three places where error handling is inconsistent." \--resume "$sid" --output-format json | jq -r '.result'claude -p "Write the fix for the first one only. Run the tests." \--resume "$sid" \--allowedTools "Read,Edit,Bash(npm test *)" -
Guarda con i tuoi occhi cosa carica
-p(e cosa--baretoglierebbe). Chiedi a Claude di elencare l’equipaggiamento con cui è partito:Terminal window # senza --bare: skill, hook e CLAUDE.md della tua macchina sono a bordoclaude -p "list the skills and hooks you have available" | head -20(= “elenca le skill e gli hook che hai a disposizione”)
Vedrai le tue skill e i tuoi hook: sono partiti con la sessione, e su un’altra macchina la stessa invocazione partirebbe con un equipaggiamento diverso — ecco il problema di riproducibilità che
--barerisolve. La controprova con--barerichiede un token (il login dell’abbonamento non viene letto), quindi ha la forma che userai in CI:Terminal window # in CI: --bare + token generato una volta con `claude setup-token`CLAUDE_CODE_OAUTH_TOKEN=... claude --bare -p "list the skills and hooks you have available"Qui la risposta sarebbe: nessuna skill, nessun hook. Stesso comando, equipaggiamento zero, risultato identico su qualsiasi macchina.
Un dettaglio che morde: i task in background
Sezione intitolata “Un dettaglio che morde: i task in background”Se Claude avvia un task Bash in background durante un run -p — un dev server, una build in watch —
quella shell viene terminata circa cinque secondi dopo che Claude ha restituito il risultato finale e
stdin si è chiuso.
I subagent e i workflow in background sono esenti da quella grazia, perché il loro risultato fa parte
dell’output finale: claude -p li aspetta. L’attesa è però limitata a dieci minuti di default, così un
agente bloccato non tiene aperto il processo all’infinito. Il tetto si regola con
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, oppure 0 per attendere senza limite.
Se fermi un run con SIGTERM — kill, un supervisore di processi — Claude Code interrompe il turno in
corso, termina l’albero dei processi di qualsiasi comando Bash attivo, esegue gli hook SessionEnd ed
esce con codice 143.
Esercizio
Sezione intitolata “Esercizio”Scrivi uno script che usi Claude come gate di qualità, e che si comporti bene quando qualcosa va storto.
- Lo script prende in input un branch e produce un verdetto su una diff.
- Deve uscire con
0se la diff è pulita e1se ci sono problemi bloccanti. - Non deve avere bisogno di nessun permesso Bash.
- Deve degradare in modo pulito se
claudefallisce.
Soluzione
#!/usr/bin/env bash# modalità prudente di bash: fermati al primo errore, vietato usare# variabili mai definite, niente errori nascosti nelle pipeset -euo pipefail
# il branch di confronto: il primo argomento dello script, oppure# "main" se non ne passi nessuno (è il significato di ${1:-main})BASE="${1:-main}"
# le differenze del tuo lavoro rispetto al punto in cui ti sei staccato# da BASE (la notazione "..." di git diff). Le catturiamo in una# variabile: le passeremo a Claude via stdin, così non gli serve# nessun permesso per leggerle.DIFF=$(git diff "$BASE"...HEAD)
# niente differenze, niente review: usciamo con 0 ("tutto bene")if [ -z "$DIFF" ]; then echo "Nessuna differenza rispetto a $BASE." exit 0fi
# Il cuore dello script: la diff entra in Claude dalla pipe, e# --json-schema obbliga la risposta ad avere ESATTAMENTE la forma# descritta qui sotto — un oggetto con una lista "bloccanti", dove# ogni voce ha file, riga, problema e scenario (tutti obbligatori:# "required"). Cosi il verdetto e un dato che lo script puo leggere,# non prosa da interpretare.RISPOSTA=$(printf '%s' "$DIFF" | claude -p \ "Review this diff. Report only defects that affect correctness or security and that have a concrete failure scenario. Ignore style, naming and formatting." \ --output-format json \ --json-schema '{ "type": "object", "properties": { "bloccanti": { "type": "array", "items": { "type": "object", "properties": { "file": { "type": "string" }, "riga": { "type": "integer" }, "problema": { "type": "string" }, "scenario": { "type": "string" } }, "required": ["file", "riga", "problema", "scenario"] } } }, "required": ["bloccanti"] }' \ --max-turns 10) || { # Claude non è riuscito a girare: non è un fallimento della review. echo "gate: claude non ha completato il run" >&2 exit 2 }
# Con --json-schema, l'output validato non è tutta la risposta: vive nel campo# structured_output dell'involucro JSON (i dettagli nel modulo 13). Da lì# leggiamo la lista "bloccanti" definita dallo schema qui sopra.N=$(printf '%s' "$RISPOSTA" | jq '.structured_output.bloccanti | length')
if [ "$N" -eq 0 ]; then echo "✔ Nessun problema bloccante." exit 0fi
echo "✘ $N problemi bloccanti:"printf '%s' "$RISPOSTA" | jq -r ' .structured_output.bloccanti[] | " \(.file):\(.riga) \(.problema)\n → \(.scenario)"'exit 1Le scelte che contano:
| Scelta | Perché |
|---|---|
| La diff via stdin | Claude non ha bisogno di nessun permesso: niente --allowedTools |
--json-schema |
Il verdetto è dato strutturato, non prosa da parsare con regex |
scenario obbligatorio nello schema |
Costringe il modello a giustificare ogni rilievo, e taglia i falsi positivi |
--max-turns 10 |
Il task non deve iterare all’infinito |
| Exit 2 distinto da 1 | 1 = review fallita, 2 = lo strumento non ha funzionato. Una CI deve poterli distinguere |
| Il ` |
Provalo:
chmod +x gate.sh./gate.sh main; echo "exit: $?"In una GitHub Action diventa:
- name: Claude gate env: CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: ./gate.sh origin/mainIl token si genera una volta con claude setup-token e si mette nei secret del repository — vedi il
modulo 14.
Errori comuni e troubleshooting
Sezione intitolata “Errori comuni e troubleshooting”| Sintomo | Causa | Soluzione |
|---|---|---|
| Lo script si blocca aspettando un permesso | In -p nessuno può approvare |
--allowedTools o --permission-mode |
| Il risultato cambia da macchina a macchina | -p carica hook, skill, MCP e CLAUDE.md dell’host |
--bare in CI |
--bare fallisce con errore di autenticazione |
Bare mode non legge le credenziali OAuth | ANTHROPIC_API_KEY, o apiKeyHelper in --settings |
Bash(git diff*) permette più del previsto |
Manca lo spazio prima dell’asterisco | Bash(git diff *) |
| Errore su stdin troppo grande | Il limite è 10 MB | Scrivi su file e cita il percorso nel prompt |
| Il run non termina mai | Un task Bash in background, o un subagent bloccato | I task Bash muoiono dopo ~5 s; per i subagent regola CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS |
| Lo script gira all’infinito consumando token | Nessun limite di turni | --max-turns |
| Non distingui un fallimento della review da un errore dello strumento | Stesso exit code | Cattura l’errore e usa codici distinti |
| Un server MCP del progetto viene caricato senza che tu lo voglia | Le sessioni -p non chiedono approvazione |
--bare, oppure --strict-mcp-config, oppure disabledMcpjsonServers |
| Un hook del repo viene eseguito in una cartella non fidata | Le sessioni -p non mostrano il dialogo di trust |
--bare su codice non tuo |
/login non funziona in -p |
I comandi solo-terminale non sono disponibili | Autentica prima, o usa CLAUDE_CODE_OAUTH_TOKEN |
| Exit code 143 inatteso | Il processo ha ricevuto SIGTERM | È il comportamento previsto: turno interrotto e SessionEnd eseguito |
Cheat-sheet
Sezione intitolata “Cheat-sheet”Le forme di base
claude -p "prompt" # esegue e stampacat file | claude -p "prompt" # legge da stdinclaude -p "prompt" --output-format json # output strutturatoclaude -p "prompt" --continue # continua l'ultima conversazioneclaude -p "prompt" --resume "$sid" # riprende una sessione per IDclaude --bare -p "prompt" --allowedTools "Read" # riproducibile, per la CII flag da ricordare
| Flag | |
|---|---|
-p, --print |
Modalità non interattiva |
--bare |
Nessun auto-discovery — per la CI |
--output-format json |
Output strutturato con metadati |
--json-schema '<schema>' |
Output validato contro uno schema |
--allowedTools "A,B(pattern *)" |
Pre-approvazione granulare |
--permission-mode dontAsk |
Solo ciò che è già in allow |
--max-turns N |
Cintura di sicurezza |
--max-budget-usd N |
Tetto di spesa |
--append-system-prompt "..." |
Aggiunge istruzioni al system prompt |
--strict-mcp-config |
Solo i server passati con --mcp-config |
--safe-mode |
Tutto disattivato, per diagnosticare |
Exit code
0 → successo≠0 → fallimento del run143 → interrotto da SIGTERMComandi CLI utili negli script
| Comando | |
|---|---|
claude setup-token |
Genera un token OAuth di lunga durata per CI e script |
claude auth status |
Stato di autenticazione in JSON |
claude doctor |
Diagnostica di installazione e settings |
claude project purge <path> |
Cancella lo stato locale di un progetto |
Pagine ufficiali consultate, verificate il 16 agosto 2026:
- Run Claude Code programmatically —
-p, bare mode, permessi, pipe, ripresa delle sessioni, task in background - CLI reference — elenco completo di comandi e flag
- Permission modes —
dontAsk,acceptEditse cosa auto-approvano - Permissions — sintassi delle regole e cosa gira prima del trust
- Authentication —
claude setup-tokeneCLAUDE_CODE_OAUTH_TOKEN - Manage sessions — ripresa per ID da directory diverse
- Environment variables —
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS
Corso indipendente, non affiliato ad Anthropic o Google. Claude e Claude Code sono marchi di Anthropic; Antigravity e Gemini sono marchi di Google.