Salta ai contenuti

12 · CLI scripting

Tempo stimato: 50 minutiPrerequisiti: la sezione Fondamenta e il modulo 9. Serve dimestichezza con bash o PowerShell.

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.

  • Eseguire Claude Code in modo non interattivo e usarne l’output in uno script.
  • Sapere cosa carica claude -p di 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.

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

Terminal window
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>

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

In una sessione -p non c’è nessuno che possa cliccare “approva”. Tre strategie, dalla più permissiva alla più blindata:

  1. Elencare i tool — l’approccio granulare:

    Terminal window
    claude -p "Run the test suite and fix any failures" \
    --allowedTools "Bash,Read,Edit"
  2. 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 *)"
  3. Impostare una modalità di base. Due modalità servono ai due estremi:

    acceptEdits per 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)

    dontAsk per la CI blindata: nega tutto ciò che non è già nelle tue regole permissions.allow o nell’insieme dei comandi di sola lettura:

    Terminal window
    claude -p "Run the checks" --permission-mode dontAsk

    Nota per i casi limite: alcuni tool restano negati in dontAsk anche con una regola di allow — quelli che per natura richiedono un umano, come AskUserQuestion (fa domande all’utente) e i tool marcati come interattivi dal loro server MCP.

La modalità non interattiva legge da stdin:

Terminal window
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt

Un esempio che si usa davvero, come script di package.json:

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.

Terminal window
# Prima richiesta
claude -p "Review this codebase for performance issues"
# Continua la più recente
claude -p "Now focus on the database queries" --continue
claude -p "Generate a summary of all issues found" --continue

Con più conversazioni in parallelo, cattura l’ID:

Terminal window
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.

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.

Terminal window
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.

  1. La forma più semplice.

    Terminal window
    cd ~/progetti/il-tuo-progetto
    claude -p "What does the auth module do?"
  2. Controlla l’exit code. È la base di ogni script serio.

    Terminal window
    claude -p "What does the auth module do?" > /dev/null
    echo "exit code: $?"
  3. 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.'
  4. 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."
  5. 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.

  6. Un revisore di sicurezza riusabile. Salvalo come review.sh:

    review.sh
    #!/usr/bin/env bash
    set -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 argomento
    gh 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 json
    Terminal window
    chmod +x review.sh
    ./review.sh 123

    "$1" è il primo argomento: bash review.sh 123 scarica la diff della PR 123.

  7. Una catena di invocazioni che condividono contesto.

    audit.sh
    #!/usr/bin/env bash
    set -euo pipefail
    sid=$(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 *)"
  8. Guarda con i tuoi occhi cosa carica -p (e cosa --bare toglierebbe). Chiedi a Claude di elencare l’equipaggiamento con cui è partito:

    Terminal window
    # senza --bare: skill, hook e CLAUDE.md della tua macchina sono a bordo
    claude -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 --bare risolve. La controprova con --bare richiede 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.

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.

Scrivi uno script che usi Claude come gate di qualità, e che si comporti bene quando qualcosa va storto.

  1. Lo script prende in input un branch e produce un verdetto su una diff.
  2. Deve uscire con 0 se la diff è pulita e 1 se ci sono problemi bloccanti.
  3. Non deve avere bisogno di nessun permesso Bash.
  4. Deve degradare in modo pulito se claude fallisce.
Soluzione
gate.sh
#!/usr/bin/env bash
# modalità prudente di bash: fermati al primo errore, vietato usare
# variabili mai definite, niente errori nascosti nelle pipe
set -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 0
fi
# 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 0
fi
echo "$N problemi bloccanti:"
printf '%s' "$RISPOSTA" | jq -r '
.structured_output.bloccanti[]
| " \(.file):\(.riga) \(.problema)\n → \(.scenario)"
'
exit 1

Le 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:

Terminal window
chmod +x gate.sh
./gate.sh main; echo "exit: $?"

In una GitHub Action diventa:

.github/workflows/gate.yml
- name: Claude gate
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
run: ./gate.sh origin/main

Il token si genera una volta con claude setup-token e si mette nei secret del repository — vedi il modulo 14.

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

Le forme di base

Terminal window
claude -p "prompt" # esegue e stampa
cat file | claude -p "prompt" # legge da stdin
claude -p "prompt" --output-format json # output strutturato
claude -p "prompt" --continue # continua l'ultima conversazione
claude -p "prompt" --resume "$sid" # riprende una sessione per ID
claude --bare -p "prompt" --allowedTools "Read" # riproducibile, per la CI

I 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 run
143 → interrotto da SIGTERM

Comandi 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:

Corso indipendente, non affiliato ad Anthropic o Google. Claude e Claude Code sono marchi di Anthropic; Antigravity e Gemini sono marchi di Google.