Salta ai contenuti

14 · Pipeline locali

Tempo stimato: 55 minutiPrerequisiti: i moduli 12 e 13. Per la parte CI serve un repository su GitHub.

Una pipeline è una catena di passaggi automatici in cui l’uscita di uno è l’ingresso del successivo: raccogli → elabora → pubblica. In questo modulo Claude diventa uno degli anelli della catena — non tutta la catena — e imparerai i tre pattern che coprono quasi tutto: la composizione con gli strumenti Unix, il fan-out (letteralmente “apertura a ventaglio”: lo stesso lavoro distribuito su tante invocazioni indipendenti, una per file) e l’integrazione in CI — il servizio che esegue controlli automatici a ogni modifica del codice, per noi GitHub Actions.

  • Comporre Claude Code con gli altri strumenti da riga di comando invece di sostituirli.
  • Elaborare centinaia di file distribuendo il lavoro su invocazioni parallele.
  • Integrare Claude Code in GitHub Actions, con l’autenticazione giusta.
  • Far fallire una pipeline quando serve, e solo quando serve.
  • Riconoscere quando Claude non è lo strumento giusto per un passaggio.

Claude Code è un pezzo della pipeline, non la pipeline. Legge da stdin, scrive su stdout, restituisce un exit code: si compone con git, jq, xargs, find. La regola pratica è che ogni passaggio che un comando deterministico può fare, deve farlo un comando deterministico. Claude serve dove serve giudizio.

Terminal window
# ✗ Sbagliato: chiedi a Claude di fare quello che grep fa meglio
claude -p "find all files containing TODO"
# ✓ Giusto: grep trova, Claude giudica
grep -rn "TODO" src/ | claude -p "group these TODOs by theme and flag the ones that look like real bugs"

Il fan-out batte il singolo prompt gigante. Per migrazioni o analisi su molti file, distribuire il lavoro su tante invocazioni claude -p è più affidabile di un unico prompt che chiede di fare tutto: ogni invocazione ha contesto pulito, gli errori sono isolati, e puoi ritentare solo i file falliti.

In CI, autentica con un token, non con il browser. claude setup-token genera un token OAuth di lunga durata, valido un anno, che autentica con il tuo abbonamento. Lo metti nei secret del repository come CLAUDE_CODE_OAUTH_TOKEN.

Terminal window
# Spiegare un errore di build
npm run build 2>&1 | tail -50 | claude -p 'spiega in un paragrafo la causa radice di questo errore'
# Rivedere una diff senza dare permessi Bash a Claude
git diff main | claude -p 'elenca i problemi di correttezza, uno per riga, formato file:riga — problema'
# Analizzare dei log
journalctl -u myservice --since "1 hour ago" | claude -p 'raggruppa questi errori per causa e stima la gravità'
# Riassumere un CSV
head -200 dati.csv | claude -p 'descrivi le colonne, i tipi e le anomalie che noti'
Terminal window
# Il risultato in un file
claude -p 'riassumi il progetto' > SUMMARY.md
# Estrarre e riusare
claude -p "elenca gli endpoint" --output-format json | jq -r '.result' | sort
# Concatenare con altri comandi
claude -p "elenca i file da migrare" \
--output-format json --json-schema '{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}}},"required":["files"]}' \
| jq -r '.structured_output.files[]' > files.txt

Il pattern documentato per le migrazioni su larga scala ha tre fasi.

  1. Genera la lista di lavoro. Fai scrivere a Claude l’elenco dei file da trattare, così il ciclo del passo successivo può leggerlo. L’esempio che seguiremo in tutto il capitolo: migrare i file che usano ancora la vecchia API di configurazione del progetto alla nuova.

    list all files that still use the legacy config API and save the list to files.txt

    (= “elenca i file che usano ancora la vecchia API di configurazione e salva la lista in files.txt”)

  2. Cicla sulla lista. Un giro di ciclo = un’invocazione di Claude = un file, ognuna con il suo contesto pulito:

    Terminal window
    # per ogni riga di files.txt...
    for file in $(cat files.txt); do
    # ...un'invocazione dedicata, che risponde solo OK o FAIL
    claude -p "Migrate $file from the legacy config API to the new one. Return OK or FAIL." \
    --allowedTools "Edit,Bash(git commit *)"
    done
  3. Prova su pochi file, poi scala. Raffina il prompt in base a cosa va storto sui primi due o tre, poi lancia sull’insieme completo. --allowedTools limita cosa Claude può fare, e conta molto quando gira senza sorveglianza.

Il ciclo minimo funziona per una dimostrazione. Per un lavoro vero servono parallelismo, log e la possibilità di ritentare:

migra.sh
#!/usr/bin/env bash
# Nota: manca apposta la "-e" (esci al primo errore): un file che
# fallisce NON deve fermare la migrazione degli altri — i fallimenti
# li gestiamo noi, marcandoli e proseguendo.
set -uo pipefail
# argomenti con default: la lista dei file e quante migrazioni in parallelo
LISTA="${1:-files.txt}"
PARALLELI="${2:-4}"
# tre cartelle di lavoro in un colpo (la brace expansion {a,b,c} le crea tutte)
mkdir -p .migrazione/{ok,fail,log}
# la funzione che migra UN file
migra_uno() {
local file="$1"
local slug
# trasforma il percorso in un nome di file valido: src/app.py -> src_app.py
slug=$(printf '%s' "$file" | tr '/' '_')
# IDEMPOTENZA (= rieseguire non fa danni): se questo file risulta gia
# migrato da un run precedente, saltalo. E cio che rende lo script
# ri-lanciabile dopo un'interruzione senza rifare tutto.
[ -f ".migrazione/ok/$slug" ] && { echo "skip $file"; return 0; }
# l'invocazione vera: prompt preciso, permessi minimi, tetto ai turni,
# e TUTTO l'output in un log dedicato a questo file (2>&1 = anche gli errori)
if claude -p "Migrate $file from the legacy config API to the new one. \
Follow the pattern in src/config/new-api.ts. Do not change behaviour. \
Run \`npm run typecheck\` when done. Reply with exactly OK or FAIL." \
--allowedTools "Read,Edit,Bash(npm run typecheck)" \
--max-turns 15 \
> ".migrazione/log/$slug.log" 2>&1
then
# Claude ha risposto: ha detto proprio "OK"?
if grep -q '^OK' ".migrazione/log/$slug.log"; then
touch ".migrazione/ok/$slug"; echo "ok $file"; return 0
fi
fi
# qualsiasi altro esito: marca il fallimento (il log dira perche)
touch ".migrazione/fail/$slug"; echo "FAIL $file"; return 1
}
# rende la funzione visibile ai processi figli che xargs lancera
export -f migra_uno
# il motore del parallelismo: xargs legge la lista (-a) e lancia fino a
# PARALLELI processi insieme (-P), chiamando migra_uno su ogni file.
# La coda "bash -c '...' _ {}" e un idioma fisso: serve solo a passare
# il nome del file alla funzione — copialo cosi com'e.
xargs -a "$LISTA" -P "$PARALLELI" -I{} bash -c 'migra_uno "$@"' _ {}
# riepilogo: conta i marcatori nelle due cartelle
echo
echo "riusciti: $(ls .migrazione/ok | wc -l) falliti: $(ls .migrazione/fail | wc -l)"
echo "per ritentare i falliti:"
# ricostruisce i percorsi dai nomi dei marcatori e li salva come nuova lista
echo " ls .migrazione/fail | tr '_' '/' > retry.txt && ./migra.sh retry.txt"

Le quattro proprietà che lo rendono usabile: idempotenza (i file già fatti si saltano), parallelismo controllato con xargs -P, log per file per capire i fallimenti, e una lista di retry invece di rifare tutto.

  1. Genera il token, una volta sola, dalla tua macchina:

    Terminal window
    claude setup-token

    Si apre lo stesso flusso di autorizzazione del browser di /login, e il token viene stampato a terminale dopo l’approvazione. Non viene salvato da nessuna parte: copialo subito.

  2. Mettilo nei secret del repository come CLAUDE_CODE_OAUTH_TOKEN:

    Terminal window
    gh secret set CLAUDE_CODE_OAUTH_TOKEN
  3. Ricorda i suoi limiti. Il token può solo fare richieste al modello: non può stabilire sessioni Remote Control né recuperare i connettori di claude.ai. I server MCP configurati localmente funzionano.

Se parti da zeroCos'è GitHub Actions e come si legge un workflow

GitHub Actions è il servizio di CI integrato in GitHub: metti un file YAML nella cartella .github/workflows/ del repository, e GitHub esegue quei passi su una sua macchina (il runner) ogni volta che accade l’evento indicato. Le parole chiave dei file che vedrai: on: = quando scattare (un push, l’apertura di una PR, un orario); jobs: = i lavori da eseguire; steps: = i passi di ciascun lavoro; uses: = “riusa un’azione già pronta”; run: = “esegui questo comando”; secrets = le credenziali custodite da GitHub, mai scritte nel file.

Il modo più semplice: uno script che gira come qualsiasi altro passo.

.github/workflows/review.yml
name: Claude review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Installa Claude Code
run: curl -fsSL https://claude.ai/install.sh | bash
- name: Review della diff
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
run: |
git diff origin/${{ github.base_ref }}...HEAD \
| claude --bare -p "Review this diff. Report only defects that affect correctness or security and that have a concrete failure scenario." \
--output-format json \
--max-turns 10 \
| jq -r '.result'

Il --bare è la scelta che conta: senza, il job eseguirebbe gli hook e i server MCP definiti nel repository, che su una PR da un fork significa eseguire codice di un estraneo.

Per l’integrazione completa — rispondere a @claude, aprire PR, commentare inline — esiste anthropics/claude-code-action.

Il setup rapido è un comando da dentro Claude Code:

/install-github-app

Installa la Claude GitHub App, configura il secret di autenticazione e prepara la PR con i file di workflow. Serve accesso admin al repository e il GitHub CLI autenticato.

Il workflow minimo, in modalità interattiva:

.github/workflows/claude.yml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Quasi tutte le righe di questo file sono la struttura fissa che ogni workflow ripete uguale (il “boilerplate” nel senso di codice-cerimonia — non confonderlo con le cartelle-template del modulo 18, che rubano lo stesso nome). Le righe che invece meritano attenzione:

Riga Perché c’è
id-token: write Richiesto dall’autenticazione di default via GitHub App
actions: read Permette a Claude di leggere i risultati della CI sulle PR
actions/checkout Dà a Claude una copia locale del repository
if: contains(...) Evita di avviare runner su commenti che non menzionano @claude
Modalità Quando Comportamento
Interattiva Il workflow non passa l’input prompt Claude aspetta la frase trigger, @claude di default, in un commento o nel corpo di una issue appena aperta. Progresso e risultati appaiono come commento
Automazione Il workflow passa prompt Claude parte senza aspettare una menzione. I risultati finiscono nel log del run, a meno che il prompt non gli dica di pubblicare

Due controlli sull’attore che ha innescato il run, e il run fallisce se uno dei due rifiuta:

  • Accesso in scrittura — su eventi di issue e pull request, l’utente deve avere accesso in scrittura al repository. Per permettere utenti specifici senza scrittura serve allowed_non_write_users, che richiede anche un token GitHub personalizzato (github_token). Attenzione al doppio taglio di quel parametro: come vedremo nel troubleshooting, passare il GITHUB_TOKEN di default impedisce ai commit di Claude di far partire la CI — quindi usalo solo se ti serve davvero questa eccezione, e in quel caso con un token di una GitHub App dedicata. Gli eventi senza autore, come un trigger schedule, saltano il controllo.
  • Attore umano — su ogni evento, un attore bot viene rifiutato a meno che non sia in allowed_bots. Serve a evitare che i bot facciano partire Claude in loop. Vale anche per i run schedulati, che GitHub attribuisce a un utente del repository, di solito l’ultimo che ha toccato il cron.
.github/workflows/report.yml
name: Daily Report
on:
schedule:
- cron: "0 9 * * *" # sintassi cron: minuto ora giorno mese giorno-settimana
# "0 9 * * *" = alle 9:00 UTC, ogni giorno
jobs:
report:
runs-on: ubuntu-latest
permissions:
contents: read
issues: read
id-token: write
steps:
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: "Generate a summary of yesterday's commits and open issues"
claude_args: |
--model claude-opus-4-8
--allowedTools "mcp__github__list_commits,mcp__github__list_issues"

Con un prompt di testo semplice, Claude non ha accesso a shell né alle API GitHub finché non gli concedi i tool che gli servono, tramite --allowedTools in claude_args o una regola permissions.allow nell’input settings. Qui legge commit e issue via API con i tool MCP di GitHub, quindi il checkout non serve.

Accetta qualsiasi argomento della CLI:

claude_args: "--max-turns 5 --model claude-sonnet-5 --mcp-config /path/to/config.json"

I più usati: --max-turns, --model, --mcp-config, --allowedTools, --debug.

  1. Una pipeline locale in un comando. Trova, giudica, formatta:

    Terminal window
    git log --since="1 week ago" --oneline \
    | claude -p "raggruppa questi commit per area funzionale. Per ogni area, una riga: area — cosa è cambiato. Niente preamboli." \
    | tee CHANGELOG-settimana.md
  2. Un gate riusabile. Riprendi lo script del modulo 12 e usalo come pre-push hook:

    .git/hooks/pre-push
    #!/usr/bin/env bash
    ./gate.sh origin/main || {
    echo "Push bloccato dal gate. Per forzare: git push --no-verify" >&2
    exit 1
    }
    Terminal window
    chmod +x .git/hooks/pre-push
  3. Un batch su molti file. Prima la lista:

    Terminal window
    grep -rln "require(" src/ > files.txt
    wc -l files.txt

    Poi due file di prova:

    Terminal window
    head -2 files.txt > prova.txt
    ./migra.sh prova.txt 1
    cat .migrazione/log/*.log

    Solo quando i due funzionano, tutto il resto:

    Terminal window
    ./migra.sh files.txt 4
  4. Porta la stessa cosa in CI. Crea il token e il secret:

    Terminal window
    claude setup-token
    gh secret set CLAUDE_CODE_OAUTH_TOKEN

    Poi il workflow review.yml visto sopra.

  5. Verifica il workflow senza aspettare una PR.

    Terminal window
    gh workflow run review.yml || gh run list --workflow=review.yml --limit 3

    Per un workflow che scatta su pull_request, apri una PR di prova con una modifica banale.

  6. Aggiungi la Action ufficiale.

    /install-github-app

    Poi menziona @claude in un commento di una issue per verificare.

Vale la pena dirlo esplicitamente, perché è l’errore più costoso in una pipeline:

Non usare Claude per Usa invece Cosa sono
Trovare pattern testuali grep, rg I cerca-testo del terminale, istantanei
Formattare codice prettier, black, gofmt I formattatori automatici (JS, Python, Go)
Applicare regole di lint deterministiche eslint, ruff I linter: trovano gli errori senza eseguire
Trasformazioni sintattiche meccaniche codemod, jscodeshift, ast-grep Strumenti che riscrivono codice seguendo una regola esatta
Validare uno schema ajv, jsonschema Validatori di JSON Schema
Contare, ordinare, deduplicare wc, sort, uniq I coltellini svizzeri Unix del testo

Claude serve dove serve giudizio: capire perché una build fallisce, decidere se un TODO è un bug, scrivere il codice che il codemod non sa scrivere, rivedere una diff. Metterlo dove basta un comando significa pagare latenza e token per un risultato meno affidabile.

Costruisci una pipeline che produca un report settimanale del repository, e falla girare da sola.

  1. Raccogli i dati con comandi deterministici: commit, issue, risultati della CI.
  2. Fai a Claude solo la parte di giudizio: cosa è successo davvero, cosa è a rischio.
  3. Produci un output strutturato, non prosa.
  4. Falla girare ogni lunedì in GitHub Actions.
Soluzione

Passi 1–3. Lo script locale, che raccoglie con git e gh e delega solo la sintesi:

report-settimanale.sh
#!/usr/bin/env bash
set -euo pipefail
DA="${1:-1 week ago}"
# Tutto cio che e deterministico lo fanno git e gh. Le graffe { ... }
# raggruppano i comandi: l'output di TUTTI finisce insieme nel file
# indicato dopo la parentesi (un solo ">" per l'intero blocco).
{
echo "## Commit"
# la storia della settimana, un commit per riga (%h=hash, %s=messaggio, %an=autore)
git log --since="$DA" --pretty='- %h %s (%an)'
echo
echo "## Issue aperte questa settimana"
# gh restituisce JSON; il suo flag --jq lo formatta al volo in righe
# markdown (e jq "annidato" dentro gh: stessa sintassi del modulo 13)
gh issue list --state open --search "created:>$(date -d "$DA" +%F)" \
--json number,title,labels \
--jq '.[] | "- #\(.number) \(.title) [\(.labels | map(.name) | join(", "))]"'
echo
echo "## Ultimi run di CI"
gh run list --limit 20 --json conclusion,name,createdAt \
--jq '.[] | "- \(.createdAt[0:10]) \(.name): \(.conclusion)"'
} > .dati-settimana.md
# A Claude solo il giudizio.
claude --bare -p "Da questi dati grezzi produci un report settimanale.
Non ripetere l'elenco dei commit: sintetizza per tema.
Per i rischi, riporta solo cose che i dati mostrano davvero, citando la prova." \
--output-format json \
--json-schema '{
"type": "object",
"properties": {
"titolo": { "type": "string" },
"sintesi": { "type": "string" },
"temi": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tema": { "type": "string" },
"cosa": { "type": "string" },
"commit": { "type": "array", "items": { "type": "string" } }
},
"required": ["tema", "cosa", "commit"]
}
},
"rischi": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rischio": { "type": "string" },
"prova": { "type": "string" },
"gravita": { "type": "string", "enum": ["alta", "media", "bassa"] }
},
"required": ["rischio", "prova", "gravita"]
}
}
},
"required": ["titolo", "sintesi", "temi", "rischi"]
}' \
< .dati-settimana.md \
| jq '.structured_output' > report.json
# Il markdown lo genera jq, non Claude: è formattazione, non giudizio.
jq -r '
"# \(.titolo)\n\n\(.sintesi)\n\n## Temi\n" +
([.temi[] | "### \(.tema)\n\(.cosa)\n\nCommit: \(.commit | join(", "))\n"] | join("\n")) +
"\n## Rischi\n" +
([.rischi[] | "- **[\(.gravita)]** \(.rischio)\n - prova: \(.prova)"] | join("\n"))
' report.json > REPORT.md
rm -f .dati-settimana.md
echo "✔ REPORT.md generato"

Tre decisioni che vale la pena notare:

Decisione Perché
I dati li raccolgono git e gh Sono esatti e gratis. Chiedere a Claude di eseguire git log costerebbe permessi, turni e token per un risultato meno affidabile
--bare Nessun CLAUDE.md né hook del repo: il report deve venire dai dati, non da istruzioni di progetto
Il markdown lo genera jq Formattare non richiede giudizio. Claude produce la struttura, la presentazione la fa un template

Passo 4. Il workflow:

.github/workflows/report-settimanale.yml
name: Report settimanale
on:
schedule:
- cron: "0 7 * * 1" # ogni lunedì alle 07:00 UTC
workflow_dispatch: # e a mano, per provarlo
jobs:
report:
runs-on: ubuntu-latest
permissions:
contents: write
issues: read
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Installa Claude Code
run: curl -fsSL https://claude.ai/install.sh | bash
- name: Genera il report
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./report-settimanale.sh "1 week ago"
- name: Pubblica come issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh issue create --title "Report settimanale $(date +%F)" --body-file REPORT.md

Due dettagli che fanno la differenza tra un workflow che funziona e uno che sembra funzionare:

  • workflow_dispatch ti permette di provarlo subito con gh workflow run report-settimanale.yml, invece di aspettare lunedì per scoprire un errore di sintassi.
  • fetch-depth: 0: senza, actions/checkout fa un clone shallow (“superficiale”: scarica solo l’ultimo commit, senza la storia) e git log --since="1 week ago" restituisce quasi nulla. È il modo più comune di ottenere un report vuoto senza capire perché.

E ricorda il vincolo di GitHub: i workflow schedulati girano solo dal branch di default, e nei repository pubblici lo schedule si disattiva dopo 60 giorni di inattività.

Sintomo Causa Soluzione
In CI il risultato non è riproducibile -p carica hook, MCP e CLAUDE.md del repository --bare
Un job in CI esegue codice di una PR da un fork Senza --bare, gli hook del repo girano --bare è obbligatorio sul codice non tuo
Il job si blocca aspettando un permesso In CI nessuno può approvare --allowedTools o --permission-mode dontAsk
git log --since non restituisce nulla in CI actions/checkout fa un clone shallow (senza storia) fetch-depth: 0
Il ciclo batch riparte da capo dopo un errore Nessuna idempotenza Marca i file già fatti e salta
Un batch consuma token all’infinito Nessun limite di turni --max-turns per ogni invocazione
Il workflow schedulato non parte GitHub li esegue solo dal branch di default Assicurati che il file sia su main
Lo schedule si è disattivato da solo Nei repo pubblici, dopo 60 giorni di inattività Riattivalo dalla UI di GitHub
@claude non risponde App non installata, secret mancante, o l’utente non ha accesso in scrittura Verifica i tre in quest’ordine
Un run schedulato viene rifiutato GitHub attribuisce i run schedulati a un utente, e i bot vengono rifiutati Aggiungi quell’attore ad allowed_bots
La CI non parte sui commit di Claude GitHub non innesca workflow sui commit fatti con il GITHUB_TOKEN di default Non passare github_token: ${{ secrets.GITHUB_TOKEN }} all’action
Il token OAuth non funziona per i colleghi È legato all’abbonamento di chi l’ha generato Per un’organizzazione, usa una API key dalla Console
Errori di autenticazione nel workflow Il token non è valido Provalo prima in locale con claude

Composizione

Terminal window
comando | claude -p "prompt" # dati in ingresso, niente permessi necessari
claude -p "prompt" | comando # risultato verso altro
claude -p "prompt" --output-format json | jq -r '.result'

Batch

Terminal window
# Fan-out sequenziale
for f in $(cat files.txt); do claude -p "..." --allowedTools "Edit"; done
# Fan-out parallelo
xargs -a files.txt -P 4 -I{} bash -c 'claude -p "... {}" --allowedTools "Edit"'

CI

Passo Comando
Generare il token claude setup-token
Metterlo nei secret gh secret set CLAUDE_CODE_OAUTH_TOKEN
Installare in CI curl -fsSL https://claude.ai/install.sh | bash
Setup della Action ufficiale /install-github-app da dentro Claude Code
Provare un workflow a mano gh workflow run <file>.yml

Il workflow minimo

- uses: actions/checkout@v6
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Permessi necessari: contents: write, pull-requests: write, issues: write, id-token: write, actions: read.

Regole d’oro

1. Ciò che un comando deterministico può fare, lo fa un comando deterministico
2. --bare in CI, sempre
3. --max-turns su ogni invocazione non sorvegliata
4. La pipe in ingresso evita di dare permessi Bash
5. Batch idempotente, con log per file e lista di retry

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.