AI-assisted debugging: usare Claude per analizzare stack trace e trovare la root cause

AI-assisted debugging: usare Claude per analizzare stack trace e trovare la root cause

Quattro ore di debugging in 20 minuti: il caso che ha cambiato il mio workflow

Il 7 febbraio 2026, martedì mattina alle 09:12, mi è arrivata una notifica dal monitoring di un'azienda del settore e-commerce B2B per cui tengo supporto on-call: un worker Horizon del loro gestionale Laravel 10 era crashato con un Fatal error durante l'esecuzione di un job di sincronizzazione notturna con l'ERP. Il sistema aveva tentato il retry tre volte con le stesse modalità, aveva fallito tutte e tre, e il job era finito in failed_jobs. Il problema concreto: la sincronizzazione doveva spedire a un ERP esterno 2.400 bolle di accompagnamento del giorno precedente prima delle 10 del mattino per rispettare lo SLA interno del cliente, e quell'ora si stava avvicinando rapidamente. Lo stack trace era lungo 47 frame, attraversava cinque livelli di closure Laravel, due service provider custom, una integrazione con un client SOAP del 2018 ancora in produzione per compatibilità con l'ERP, e terminava in una riga di SPL con un errore Trying to access array offset on value of type null che non diceva assolutamente nulla su cosa fosse davvero null e perché.

Quattro anni fa, con lo stesso stack trace di fronte, avrei passato tre o quattro ore in debugging: lettura del trace frame per frame, ipotesi progressive su quale parametro potesse essere null, test locali con riproduzioni dei job falliti, controlli incrociati sui log MySQL slow query, check di congruenza dati fra ERP e gestionale. È esattamente quello che hai fatto mille volte se lavori sul serio con PHP in produzione: un lavoro di pura archeologia fatto a mano. Quel martedì mattina, invece, ho aperto un terminale con Claude Code attivo, ho strutturato il contesto seguendo il workflow che descriverò nelle prossime sezioni, e ho avuto la root cause conclamata in 22 minuti netti: un cambio recente del formato di risposta dell'ERP (un tag XML opzionale era diventato assente su alcuni record emessi con codice fiscale vuoto), un passaggio del parser SOAP che assumeva il tag presente, e il null che si propagava per cinque frame prima di esplodere in un punto lontano dal vero problema. Il fix - tre righe di codice con un null coalescing e un fallback - è stato scritto, testato con un replay del job fallito e deployato in produzione in altri venti minuti. La sincronizzazione è ripartita alle 09:56, i 2.400 documenti sono passati all'ERP in tempo, lo SLA è stato rispettato.

Questo articolo descrive il workflow che uso per l'AI-assisted debugging, con il prompt esatto che invio a Claude, la struttura di contesto che produce i risultati più affidabili, e i guardrail operativi che servono a evitare che l'assistente diventi un generatore di false piste plausibili. Non è un articolo di entusiasmo per gli LLM come strumento magico: è la descrizione operativa di come uno strumento probabilistico può accelerare un processo deterministico come il debugging a patto di essere inquadrato in un metodo rigoroso.

Perché Claude è più efficace di una ricerca su Stack Overflow per stack trace complessi

La domanda legittima che ogni dev senior si pone la prima volta è: "Ma questo non lo facevo già con grep, tail e cinque tab aperti su Stack Overflow?". In parte sì, ma la differenza emerge su stack trace profondi in codebase medio-grandi. Una ricerca su Stack Overflow (o su ChatGPT usato come ricerca sostitutiva) funziona bene quando l'errore è noto, il pattern è generico e la soluzione è canonica - un undefined index in una query Eloquent, un class not found dopo un upgrade di Composer, un maximum execution time exceeded in un job batch. Funziona male quando l'errore è specifico del tuo dominio: uno stack trace che attraversa il tuo service layer custom, il tuo client SOAP verso un ERP particolare, le tue closure di binding nel container, e un edge case nei dati che non si riproduce in staging.

La differenza tecnica è che Claude Code, quando gli fornisci il file con lo stack trace e i file sorgente dei frame rilevanti e un estratto strutturato di log, può correlare informazioni che non esistono in nessun altro posto perché sono specifiche della tua codebase. Stack Overflow non può sapere che nel tuo app/Services/ErpClient.php c'è un metodo parseResponse() che assume la presenza di un tag XML specifico; solo il tuo codice lo sa, e se glielo dai a Claude lui lo usa. Il giudizio di valore che faccio è: per il 40% dei bug "facili" (sintassi, configurazione, errori ovvi) Stack Overflow o una ricerca AI qualunque vanno bene; per il 40% intermedio (bug logici in contesti specifici) Claude Code con il contesto della codebase ti dà un vantaggio di tempo significativo; per il 20% più tosto (bug di concorrenza, race condition distribuite, problemi di integrazione fra sistemi legacy) l'AI serve come acceleratore del ragionamento, non come risolutore. La root cause resta una questione di comprensione umana, l'LLM aiuta a restringere lo spazio di ricerca.

Sul workflow del martedì che ho raccontato in apertura, la chiave del risultato in 22 minuti non è stata "Claude ha trovato il bug". È stata questa sequenza: io ho strutturato il contesto (stack trace, file rilevanti, log ERP, payload del job fallito), Claude ha proposto cinque ipotesi ordinate per probabilità, io ho scartato tre come incompatibili con dati concreti che conoscevo (es: "è escluso che sia un problema di autenticazione, il log SOAP mostra auth OK"), Claude ha affinato le due rimanenti chiedendomi informazioni specifiche su payload di risposta, io ho estratto tre payload di job falliti recenti, lui ha identificato il pattern (il tag XML assente nei record con codice_fiscale=''), io ho verificato la ipotesi aprendo il parser e confermando l'assunzione nascosta. La root cause emerge dalla collaborazione strutturata, non dall'oracolo.

Il workflow strutturato: come preparare il contesto prima di invocare Claude

Il singolo fattore che più determina la qualità del risultato in AI-assisted debugging è la preparazione del contesto. Un prompt del tipo "Claude, ho questo stack trace, aiutami" ti darà una risposta generica. Un prompt strutturato con cinque elementi ben separati ti darà ipotesi mirate e verificabili.

I cinque elementi che includo sempre, nell'ordine esatto, sono: contesto applicativo (cosa fa il sistema, che versioni di PHP/framework, che architettura), stack trace completo (non troncato - spesso il frame killer è nei frame più bassi, non in quelli di testa), file sorgente dei frame applicativi rilevanti (non tutti, solo quelli non di framework - il codice di Laravel o Symfony il modello lo conosce già), sintomo osservabile (quando si manifesta, con che frequenza, in quali condizioni), e cosa ho già verificato (per evitare che il modello proponga piste che ho già scartato). Il template operativo che uso è questo, in file markdown passato a Claude Code con @:

== Contesto applicativo ==
Applicazione Laravel 10.45, PHP 8.2, MySQL 8.0 dietro Nginx.
Architettura: Horizon gestisce 8 worker su una coda "erp-sync".
Il job SincronizzaBolle e' schedulato ogni notte alle 02:00 via Laravel Scheduler.
Dipendenze critiche: guzzlehttp/guzzle 7.8, mikehaertl/phpwkhtmltopdf 2.5,
client SOAP custom verso ERP proprietario del cliente.

== Stack trace completo ==
[qui incollo i 47 frame letteralmente, senza tagliare]

== File sorgente dei frame applicativi ==
Allegati: app/Jobs/SincronizzaBolle.php, app/Services/ErpClient.php,
app/Services/Parsers/BollaResponseParser.php.

== Sintomo osservabile ==
Fallimento in failed_jobs con il messaggio d'errore:
"Trying to access array offset on value of type null"
Frequenza: 3 job falliti su 2.400 la notte del 2026-02-07.
Pattern: non deterministico, job con stessi payload a volte passano.
Prima occorrenza osservata: 2026-02-05 (tre notti fa).
Prima di questa data il job girava pulito da 14 mesi.

== Cosa ho gia' verificato ==
L'auth SOAP verso l'ERP e' OK (log di successo nei tre falliti).
Il payload di input al job e' coerente con lo schema atteso.
L'infrastruttura (MySQL, Redis, Horizon) e' sana, nessun restart recente.
La versione di Composer non e' cambiata nelle ultime due settimane.

Questo file è il pacchetto che invio come contesto a Claude. La scelta di scartare i frame di framework (Laravel core, SPL) dalla lista dei file allegati è deliberata: il modello conosce perfettamente come funziona Illuminate\Queue\Worker o Illuminate\Foundation\Application, mentre non conosce il tuo BollaResponseParser. Allegare i file del framework è rumore; allegare i tuoi è segnale. La scelta di includere "cosa ho già verificato" è quella che ha il maggior impatto sulla qualità delle ipotesi: senza quella sezione, il modello perde tempo a proporti di controllare l'auth SOAP, ignorando che lo hai già fatto; con quella sezione, parte da ipotesi più sofisticate e che probabilmente non avevi ancora esplorato.

Il martedì mattina del caso concreto, la sezione "cosa ho già verificato" conteneva altre quattro voci che avevo controllato nei primi cinque minuti (integrità del Redis di Horizon, uptime dell'ERP, dimensione della coda failed_jobs, assenza di memory leak sui worker). Il modello, con quella base, ha immediatamente scartato le ipotesi "infrastrutturali" e si è concentrato su ipotesi applicative - che è dove il bug effettivamente era.

Stai cercando un Consulente Informatico esperto per introdurre workflow di AI-assisted debugging nel tuo team di sviluppo PHP, calibrati sulla tua codebase senza rischio di regressioni? Nel mio profilo professionale trovi l'esperienza concreta su integrazione di Claude Code e MCP server custom in pipeline di sviluppo professionali.

Il prompt operativo: cosa chiedere e come chiederlo

Una volta che il contesto è strutturato, il prompt effettivo che invio a Claude è sorprendentemente breve e molto vincolante. La brevità è intenzionale: un prompt prolisso fa divergere il modello, un prompt vincolante lo costringe a strutturare l'output in modo verificabile. Il template che uso è questo:

Ho un bug in produzione. Il contesto completo è nel file allegato
debug-context.md (con gli allegati applicativi referenziati).

OBIETTIVO: identificare la root cause.

METODO:
1. Analizza lo stack trace dal basso verso l'alto (frame 47 -> frame 0),
   non il contrario.
2. Per ogni frame applicativo, verifica se fa assunzioni implicite
   sui dati (es. array-offset access senza null check,
   metodi chiamati su risultati di fetch senza verifica).
3. Proponi le TRE ipotesi più probabili di root cause, ordinate
   per probabilita' decrescente, ognuna con:
   - ipotesi in una frase
   - evidenza specifica nei file allegati che la supporta
   - esperimento verificabile che la confermerebbe o smentirebbe
4. Non proporre ipotesi generiche (es. "potrebbe essere un bug
   di framework"). Solo ipotesi ancorate al codice allegato.
5. Se non hai evidenza sufficiente per tre ipotesi, proponine due
   e dichiara esplicitamente cosa ti serve di piu' per formularne
   una terza.

OUTPUT: formato markdown con sezioni chiare per ogni ipotesi.

Quattro elementi di questo prompt meritano attenzione. Primo: l'istruzione di leggere il trace dal basso verso l'alto è contraria all'istinto umano (tendiamo a leggere dall'alto, il frame più "recente" dello stack) ma è quella che produce risultati migliori sui bug logici, perché il frame originario del problema è spesso in fondo al trace, mentre i frame in cima sono il punto di propagazione. Secondo: la richiesta di evidenza specifica "nei file allegati" forza il modello a citare righe concrete invece di speculare in astratto. Terzo: la richiesta di esperimento verificabile trasforma le ipotesi in azioni - "verifica se i payload del job 42, 183 e 517 hanno codice_fiscale=''" è un esperimento, "forse ci sono dati sporchi" è un'opinione. Quarto: il rifiuto di ipotesi generiche disincentiva il modello dall'offrire piste che non valgono la pena di seguire.

Sul caso del martedì, Claude mi ha proposto esattamente tre ipotesi: (A) il parser XML del client SOAP non gestisce risposte con tag opzionali mancanti; (B) un race condition fra due worker Horizon che modificano lo stesso record della tabella bolle_in_invio; (C) una regressione nel driver guzzlehttp/guzzle dopo l'ultimo update patch. Per ognuna aveva citato righe specifiche del codice e proposto un esperimento. Ho eseguito i tre esperimenti in sequenza, scartando B (il codice ha un lock pessimistico lockForUpdate che impedisce la race, Claude lo aveva notato ma l'aveva elencato comunque) e C (l'update di Guzzle era di due mesi prima, incompatibile con "prima occorrenza tre giorni fa"), confermando A. La verifica dell'ipotesi A ha richiesto di aprire BollaResponseParser.php e controllare la riga 87 che Claude aveva citato: effettivamente faceva $response['CodiceFiscale'] senza null check, e bastava grep sui payload di risposta dei tre job falliti per confermare che quel nodo era assente.

La sezione "cosa NON chiedere a Claude": dove il modello fallisce sistematicamente

Esistono tre classi di problemi dove l'AI-assisted debugging produce risultati mediocri o pericolosi, e vale la pena essere espliciti su quali sono per evitare di investire tempo nel posto sbagliato. Primo: i bug che dipendono da stato transitorio non riproducibile in statica. Esempio tipico, una race condition fra due processi che si manifesta solo sotto carico reale con pattern di timing specifici. Claude può leggere il codice e dirti "questa sezione non è thread-safe", ma senza avere accesso runtime al sistema in crisi non può confermarlo con certezza. Su questa classe di bug lo uso per generare le ipotesi, ma la verifica richiede strumenti di tracing runtime (strace, tcpdump, Xdebug triggered profiling) che l'LLM non sostituisce.

Secondo: i bug di integrazione con sistemi esterni non documentati. Esempio: un ERP proprietario che cambia silenziosamente il formato di risposta in una release minore non comunicata. Claude non ha accesso alla documentazione del sistema esterno, e se gli chiedi "perché questo payload dell'ERP contiene un nuovo campo?" ti darà una risposta plausibile ma potenzialmente inventata. Su questa classe di bug l'approccio giusto è usare Claude per analizzare i payload storici (dove ha evidenza concreta), non per ipotizzare sulle intenzioni del sistema esterno (dove non l'ha). Ho affrontato in dettaglio il pattern di integrazione con ERP legacy e le insidie che questo genera nel mio approfondimento sull'integrazione di ERP legacy con API PHP, i pattern e le insidie ricorrenti.

Terzo: i bug che richiedono conoscenza profonda di infrastruttura proprietaria. Esempio: un problema di performance specifico della tua architettura di rete, del tuo load balancer custom, del tuo tuning di MySQL fatto cinque anni fa da un DBA che non c'è più. Claude conosce MySQL in astratto, non il tuo my.cnf con le sue bizzarrie storiche. Su questa classe di bug il contesto da fornire al modello è sproporzionato - copiare tutta la configurazione, i log del DB, le statistiche runtime - e spesso è più veloce analizzare a mano con gli strumenti che già hai. Il modello resta utile per suggerire quali strumenti usare ("prova a vedere SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_%'"), meno per l'interpretazione dei risultati che l'esperienza specifica sul tuo sistema rende più veloce.

Il guardrail più importante: la verifica incrociata prima del fix

Il rischio maggiore dell'AI-assisted debugging non è che Claude sbagli - è che produca risposte plausibili ma sbagliate in modo così coerente da convincerti a fixare qualcosa che non è davvero il bug. Il pattern è pericoloso: il modello ti propone una root cause, tu non hai tempo di verificarla rigorosamente perché il bug è in produzione adesso, applichi il fix, il bug temporaneamente sembra risolto (magari perché è raro e ti sembra sparito), ma in realtà hai aggiunto un workaround inutile a un bug che resta latente e torna a presentarsi due settimane dopo in forma diversa.

Il guardrail che applico senza eccezioni, anche nelle emergenze, è riprodurre il bug su un ambiente controllato prima del fix. Nel caso del martedì, dopo che Claude ha proposto l'ipotesi A (tag XML opzionale assente), non ho applicato il fix direttamente. Ho estratto tre payload di job falliti dal database failed_jobs, ho scritto un test Pest che passava quei payload al BollaResponseParser, ho verificato che il test falliva con lo stesso errore del produzione, poi ho applicato il fix (due righe: $response['CodiceFiscale'] ?? '') e ho verificato che il test passava. Solo a quel punto ho mergiato e deployato. Il tempo totale di questa verifica: 12 minuti, su un budget di 20 minuti per l'intero ciclo. È il tempo meglio speso di tutta la sessione - senza quella verifica, sarei potenzialmente finito in un loop di "fix veloce poi torna il bug" che aumenta il debito tecnico invece di ridurlo. Lo stesso principio è quello che applico quando costruisco suite di test minimi su codebase legacy, discusso nel mio articolo sulle pratiche di introduzione di test in un PHP legacy, con smoke test harness e snapshot testing.

Il secondo guardrail è non accettare mai fix che il modello non può spiegare rigorosamente. Se Claude ti dice "aggiungi ?? '' alla riga 87 e dovrebbe funzionare" ma non ti sa dire perché $response['CodiceFiscale'] a volte è null - quale sequenza di eventi produce quel null, quale input dell'ERP, quale branch del parser - il fix è un workaround alla cieca, non una soluzione. Un fix senza comprensione della root cause è tecnicamente equivalente a un try/catch { /* ignore */ }: sopprime il sintomo senza risolvere il problema. La mia regola operativa è che la risposta di Claude debba contenere una frase che inizia con "questo succede perché..." ancorata a evidenza concreta nel codice o nei dati. Se non c'è, rigetto il fix proposto e approfondisco.

Metriche del workflow: cosa ho misurato su due anni di utilizzo

Tengo un registro approssimativo dei debugging AI-assisted che ho fatto nei progetti dei miei clienti negli ultimi 18 mesi. Su circa 120 sessioni registrate, la distribuzione dei tempi è questa: 78 sessioni concluse in meno di 30 minuti (65%), 31 sessioni in 30-90 minuti (26%), 11 sessioni oltre le 90 minuti o abbandonate (9%). Confrontato con la stima soggettiva del tempo che avrei impiegato senza AI-assist - stima imperfetta ma ancorata alla mia esperienza di 20 anni su bug simili - il risparmio medio è nell'ordine del 55-65% sulle sessioni concluse. Le sessioni abbandonate sono sempre state quelle nelle classi di bug "non gestite bene dall'AI" descritte sopra.

L'altra metrica che tengo è il tasso di false piste: quante volte Claude propone una root cause che dopo verifica rigorosa si rivela sbagliata. Su 120 sessioni, 14 volte la prima ipotesi proposta era errata ma la seconda o terza erano corrette. 4 volte tutte e tre le ipotesi erano sbagliate e il bug l'ho trovato da solo senza assistenza. 0 volte, fortunatamente, ho applicato un fix proposto da Claude senza verifica e mi sono accorto dopo che era sbagliato - merito non del modello ma del guardrail di "riproduci e testa prima del fix". Il tasso di errore "catastrofico" è zero a oggi, ma è zero solo finché il guardrail resta in piedi. Nel momento in cui un dev del team dovesse scorciarlo per emergenza, il primo fix sbagliato sarebbe questione di tempo.

La metrica più importante però non è il tempo né la correttezza, è la profondità di ragionamento che resta al team dopo la sessione. Un debugging fatto bene lascia al team comprensione del sistema - il dev che risolve impara qualcosa sulla codebase e sulla relazione fra i componenti. Un debugging fatto "spingendo il bottone di Claude" rischia di lasciare zero apprendimento, perché la risposta arriva dall'esterno senza passare per il ragionamento del dev. Il workflow che ho descritto - con preparazione del contesto, valutazione critica delle ipotesi, riproduzione del bug, verifica del fix - garantisce che il dev resta l'autore del pensiero, con Claude come acceleratore. È un equilibrio delicato, ma è quello che distingue un uso professionale dell'AI da una dipendenza superficiale. Se gestisci un team di sviluppo che passa molto tempo in debugging di bug complessi e vuoi introdurre un workflow AI-assisted strutturato senza scivolare nella "AI-magic" che produce fix fragili e developer che smettono di pensare, contattami per una formazione pratica: in una giornata di lavoro con il tuo team analizziamo tre bug reali della vostra codebase, definiamo il template di contesto calibrato sulla vostra architettura, e stabiliamo i guardrail operativi che trasformano Claude in un acceleratore senza eroderne la qualità del ragionamento.

Ultima modifica: