# OpenQodex 0.11.0 alla prova: perché Git push può passare con una review incompleta

> Canonical: https://vxi.me/it/posts/openqodex-prepush-review-fail-open/
> Locale: it-it
> Published: 2026-10-11
> Updated: 2026-10-11
> Markdown: https://vxi.me/it/posts/openqodex-prepush-review-fail-open.md

Inserire una code review basata su AI in un hook Git pre-push sembra un'idea semplice: prima di inviare il codice al remoto, uno strumento lo controlla. Se la modifica non è stata esaminata o emergono problemi gravi, il push si ferma.

Ho provato [OpenQodex](https://github.com/openqodex/openqodex) 0.11.0 in più test con repository Git isolati e usa-e-getta. ShellCheck ha effettivamente riconosciuto un errore che avevo introdotto apposta in uno script. Anche il vero hook Git, con una configurazione adeguata, ha respinto un push non revisionato. La questione più interessante è arrivata con un Reviewer AI indipendente che non riusciva a terminare: una revisione incompleta non impedisce necessariamente il push.

Questo **non dimostra che le review AI di OpenQodex siano scadenti**. La parte relativa al modello non è stata completata con successo. Bisogna piuttosto distinguere tre componenti: la scansione statica, il Reviewer indipendente e il controllo pre-push che consulta ricevute di revisione già esistenti (Receipt). Installare un hook non significa eseguire automaticamente tutti e tre i passaggi.

## ShellCheck ha trovato il problema, ma solo quando era davvero in esecuzione

Ho preparato un repository Git eliminabile con due versioni dello stesso script Shell. Nella versione iniziale una variabile era racchiusa tra doppi apici; nella versione modificata ho tolto deliberatamente gli apici per provocare la regola `SC2086` di ShellCheck.

Il test era limitato alla scansione con OpenQodex 0.11.0, senza chiamare alcun modello. Gli argomenti principali erano questi:

```console
openqodex scan --uncommitted --only shellcheck --block-on-severity nitpick
```

La riga è semplificata per facilitarne la lettura. Lo script di test reale specificava anche `--cwd`, `--report-dir` e il percorso fisso del pacchetto verificato. **Non sto affermando di aver eseguito quella precisa riga, identica carattere per carattere.** Per riprodurre il controllo serve un vero diff in un repository isolato e la stessa versione della CLI. I parametri sono descritti nella [documentazione relativa al commit fissato](https://github.com/openqodex/openqodex/blob/2f2fa9e48c1f62a6123997eb14111140d8e22a74/docs/plumbing.md).

A parità di modifica sintetica, i risultati erano molto diversi a seconda della disponibilità di ShellCheck.

| Condizione | Stato ShellCheck | Risultati finali | Codice di uscita |
| --- | --- | --- | --- |
| Offline, installazione vietata | `not_installed` | 0 | 0 |
| Installazione ed esecuzione consentite | `ran` | 1 (`SC2086`) | 1 |

Sono i dati depurati del test sintetico del 10 ottobre 2026 con versione fissata. Nell'esecuzione online il rapporto riportava `rawCount=1` e `keptCount=1`, oltre a `SC2086` tra i risultati. Nella prova offline, invece, ShellCheck non era installato: la scansione non è mai partita. I suoi `0 findings` non sono una prova di codice corretto.

Conta anche la severità assegnata. In questa mappatura `SC2086` è classificato come `nitpick`, mentre `review.severity_threshold` in OpenQodex ha come valore predefinito `minor`. Guardando solo i risultati visibili con la configurazione standard, si rischia di pensare che non sia stato trovato nulla. Era un filtro che avevamo già incontrato; per questo il test ha esplicitato `--block-on-severity nitpick`, in modo che l'errore artificiale restasse nel rapporto.

Da allora non mi basta più vedere un codice di uscita 0. Voglio sapere, prima di tutto, **se lo scanner sia stato eseguito davvero**. Poi controllo quali risultati originali siano sopravvissuti ai filtri di severità e delle righe modificate. Un tool assente non deve sembrare una verifica superata.

## Il vero hook pre-push non avvia automaticamente l'AI a ogni push

Nella seconda prova ho usato un comando Git push reale, indirizzato però a un repository bare locale. Nessun progetto di produzione e nessun remote ospitato sono stati coinvolti.

Ho installato l'effettivo hook pre-push di OpenQodex e creato un commit senza ricevuta di review. Con la configurazione predefinita l'hook ha avvisato che mancava una revisione, ma `git push` è riuscito e il ramo nel bare remote è avanzato.

Ho quindi aggiunto:

```yaml
version: 1
review:
  block_on_severity: major
```

Con un nuovo commit ancora privo di review, il push è stato invece rifiutato. Il test ha verificato anche che il `main` del bare remote continuasse a puntare al commit precedente. Non era uno script finto che restituisce sempre 1: sono stati esercitati gli hook realmente installati da OpenQodex.

| Caso sintetico di push | Comportamento osservato |
| --- | --- |
| Impostazioni predefinite, nessun Receipt | Avviso, ma push consentito |
| `block_on_severity: major`, modifica ancora non revisionata | Push rifiutato, HEAD remoto invariato |

Questa verifica dimostra soltanto cosa succede **quando non esiste una ricevuta di revisione**. Non è stato avviato Codex come Reviewer indipendente e non è stato prodotto alcun Receipt valido per una review AI completata.

Il [codice sorgente dell'hook alla versione fissata](https://github.com/openqodex/openqodex/blob/2f2fa9e48c1f62a6123997eb14111140d8e22a74/packages/cli/src/commands/hook.ts) spiega la separazione. Al momento del push, il comando individua l'intervallo Git da inviare, cerca in OpenQodex HOME dell'utente una ricevuta relativa alla modifica e prende una decisione. Non lancia nuovamente tutti gli scanner né avvia un secondo modello AI a ogni `git push`.

Perciò «`scan` è stato eseguito», «`review` è stata completata» e «`hook` ha permesso il push» sono tre affermazioni distinte. Verificarne una non significa avere verificato le altre.

## Perché un Reviewer fallito può lasciare passare il push?

Il terzo tentativo ha mostrato il caso più scomodo. Su un Mac, il processo indipendente di Codex Reviewer ha iniziato una richiesta, ma ha ricevuto HTTP 401. OpenQodex ha contrassegnato l'esito come `incomplete`: non esisteva una ricevuta valida di revisione completata. **Quel tentativo non ci dice quanto sia bravo il modello a trovare bug, né verifica il ciclo completo di Receipt validi e obsoleti.**

Il riepilogo del test su Mac riportava inoltre una sequenza da approfondire: prima un push senza review era stato rifiutato; dopo il tentativo `incomplete`, un altro push aveva restituito codice 0 e creato un ramo remoto. Ma i due push non avevano esattamente lo stesso ref di destinazione e i rapporti originali del Mac non sono stati incorporati come prove verificabili indipendentemente. Non è corretto presentare quella sequenza come una riproduzione causale completa del passaggio da errore del Reviewer a push riuscito.

Ciò che possiamo verificare direttamente è il **comportamento previsto dal codice sorgente di OpenQodex 0.11.0**.

Nel file [`push-gate.ts` fissato a quel commit](https://github.com/openqodex/openqodex/blob/2f2fa9e48c1f62a6123997eb14111140d8e22a74/packages/core/src/push-gate.ts), `checkPush()` restituisce `abstain` quando trova un Receipt relativo esattamente alla modifica corrente e con `kind` pari a `incomplete`. In altre parole, sceglie di non bloccare il push. Impostare `review.block_on_severity: major` non trasforma quel ramo in un rifiuto.

Nel [Git hook generato da `hook.ts`](https://github.com/openqodex/openqodex/blob/2f2fa9e48c1f62a6123997eb14111140d8e22a74/packages/cli/src/commands/hook.ts) c'è un secondo percorso: lo script rifiuta il push solo se il sottocomando termina con codice 1. Altri errori dello strumento, compreso un processo che non riesce ad avviarsi, vengono convertiti in uscita 0. È una scelta comprensibile per non rendere impossibile usare Git quando la review non funziona. Ma significa anche che **una soglia di severità non impone il completamento corretto e obbligatorio della review AI**.

È una differenza di politica in caso di guasto, non la dimostrazione di una vulnerabilità che permetta qualsiasi aggiramento in produzione. Un progetto può scegliere di non interrompere il lavoro degli sviluppatori se il Reviewer è indisponibile; un'organizzazione può esigere l'opposto. Il problema nasce quando si pensa che la prima scelta implementi automaticamente la seconda.

## Cosa farei se la review dovesse essere davvero obbligatoria

Per il momento userei OpenQodex come strumento di feedback per chi sviluppa, non come unico gate di merge impossibile da aggirare. Lo scanner fornisce regole e posizioni dei problemi. L'hook segnala l'assenza di review. La decisione di unire un commit, invece, dovrebbe spettare a un controllo CI separato, osservabile e verificabile, capace almeno di distinguere:

- Se tutti gli scanner richiesti sono realmente partiti oppure risultano `not_installed`, saltati o in errore.
- Se il Reviewer AI indipendente ha concluso il lavoro o ha restituito `unavailable` / `incomplete`.
- Se il Receipt riguarda il commit / diff attuale ed è stato calcolato con la politica di severità corretta.
- Se la protezione del ramo richiede davvero il successo del required check anche quando la CI o il servizio di review non sono disponibili.

**Questa è una raccomandazione architetturale, non una soluzione di branch protection GitHub già implementata e collaudata nel nostro test.** Se un team vuole impedire ogni merge privo di review completata, deve verificare separatamente che il proprio controllo CI sia davvero fail-closed. La configurazione locale pre-push non offre da sola quella garanzia.

In un repository personale, poi, non sono convinto che convenga chiamare un modello a ogni push. Eseguire `review` quando serve, tenere le scansioni statiche come feedback rapido e usare una CI effettivamente testata per i rami importanti potrebbe essere un compromesso migliore tra costi e flusso di lavoro. Bisogna soltanto evitare di confondere «hook installato» con «questa modifica è stata esaminata da un'AI indipendente».

Le conclusioni verificabili di questa serie sono volutamente limitate: OpenQodex 0.11.0 ha trovato `SC2086` in una modifica Shell sintetica; un Git remote usa-e-getta ha mostrato il default di avviso senza blocco e il rifiuto configurato di un push non revisionato; il codice fissato contiene inoltre percorsi espliciti in cui un Receipt `incomplete` o certi errori dell'hook non bloccano. **La revisione indipendente del modello non è mai stata completata con successo.** Non abbiamo dati attendibili su accuratezza, falsi positivi, latenza o verifica end-to-end di un Receipt valido.

Proprio per questo non affiderei al solo hook locale di OpenQodex il ruolo di unica review AI obbligatoria per un repository di produzione. Resta utile per avviare scansioni e revisioni. Prima di lasciargli decidere se un push è consentito, però, vorrei sapere chi lo ferma quando il Reviewer non riesce a completare il lavoro.
