SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-13

Agente AI per review delle PR su un VPS

Configura un reviewer AI self-hosted: diff limitato, filtri per percorso e dimensione, commenti inline e costo reale per ogni pull request.

Che cosa fa un agente self-hosted per la revisione delle PR

Un agente self-hosted per la revisione delle PR è un piccolo programma eseguito su un server di tua proprietà. Legge il diff di una pull request (PR) e invia a un modello soltanto le righe modificate. Il risultato viene pubblicato come commenti inline nella revisione. Non esegue mai il checkout del tuo branch e non legge mai un file che la pull request non ha modificato. Le uniche credenziali che conserva sono una chiave API (application programming interface) del modello e un token che può aggiungere commenti, ma non eseguire altre operazioni.

Un modello sa leggere un diff. Questo problema è risolto. La questione importante è dove viene inviato il diff e chi conserva la chiave. Un bot di revisione hosted significa che ogni diff di ogni repository privato lascia la tua rete, finisce nei log di un terzo e resta soggetto alla relativa policy di conservazione. Su un VPS (virtual private server) di tua proprietà, il diff passa da GitHub al tuo server e poi all'API del modello; puoi inoltre leggere le quaranta righe di codice che determinano che cosa viene inviato.

Cosa serve prima di iniziare

  • Un VPS con Ubuntu 24.04 e un runner self-hosted di GitHub Actions già registrato nel repository. Assegnagli anche l'etichetta pr-review durante la registrazione, perché il workflow seguente seleziona i runner con questa etichetta.
  • Una chiave API Anthropic ottenuta dalla Claude Console.
  • Un repository in cui controlli chi può aprire una pull request. Un repository privato è il caso più semplice. La sezione seguente sui fork tratta il caso dei repository pubblici e la relativa risposta è meno rassicurante.

Installa il reviewer sulla VPS

Il servizio runner viene eseguito con l’account senza privilegi che hai creato eseguendo ./svc.sh install. Installa il reviewer con lo stesso account, in modo che il job possa eseguirlo senza sudo. Sostituisci runner qui sotto con il nome del tuo account.

sudo apt update && sudo apt install -y gh python3-venv
sudo install -d -m 755 -o runner -g runner /opt/pr-review
sudo -u runner python3 -m venv /opt/pr-review/venv
sudo -u runner /opt/pr-review/venv/bin/pip install anthropic
gh --version

gh --version restituisce gh version 2.45.0 su Ubuntu 24.04 ad agosto 2026. Qualsiasi release dalla 2.20 in poi include il flag --input usato di seguito. Un messaggio Command 'gh' not found indica che il componente universe non è abilitato; esegui sudo add-apt-repository universe e riprova.

Dove risiedono la chiave e il token

Due secret, due durate diverse. Nessuno dei due deve essere inserito nel repository.

ANTHROPIC_API_KEY è un secret del repository, impostato in Settings, quindi Secrets and variables, quindi Actions. GitHub lo cifra e lo inserisce nell'ambiente dello step durante l'esecuzione. Non è mai un file su disco e non compare mai nella cronologia di git.

GITHUB_TOKEN funziona in modo diverso. Actions genera un token nuovo per ogni job e lo revoca al termine del job. Le operazioni consentite a quel token sono definite dal blocco permissions: nel workflow. È qui che si applica effettivamente il principio del privilegio minimo:

permissions:
  contents: read
  pull-requests: write

Quel token può pubblicare una review. Non può eseguire il push di un commit, unire un branch, modificare un file di workflow o accedere a un altro repository. Un agent che può pubblicare commenti è un reviewer. Un agent che può eseguire il push è un committer, e nessuno lo ha autorizzato. Tratta la model key con la stessa attenzione, perché utilizza denaro del tuo account. Ulteriori informazioni su questo tipo di problema sono disponibili in tenere i secret fuori dalla portata di un agent AI.

Actions sostituisce la stringa esatta del secret con *** nei log dei job. Il confronto riguarda soltanto la stringa esatta. Di conseguenza, una key codificata in base64, suddivisa su due righe o stampata un carattere alla volta compare in chiaro. Non aggiungere uno step di debug che scarichi il contenuto dell'ambiente.

Perché una pull request da un fork non vede la tua chiave API

La regola di GitHub è semplice: ad eccezione di GITHUB_TOKEN, i secret non vengono passati al runner quando un workflow viene attivato da un repository fork. Di conseguenza, un'esecuzione pull_request avviata da un fork esegue lo script senza ANTHROPIC_API_KEY e la prima chiamata API fallisce con invalid x-api-key.

La correzione più immediata sarebbe impostare il trigger su pull_request_target, che esegue il workflow nel contesto del repository di base e rende disponibili i secret. In questo caso non farlo. Le indicazioni di sicurezza di GitHub specificano che questi workflow "sono privilegiati, cioè condividono la stessa cache del branch principale con gli altri trigger di workflow privilegiati e possono avere accesso in scrittura al repository e ai secret a cui fanno riferimento", aggiungendo che il risultato "può essere sfruttato per assumere il controllo di un repository".

Le stesse indicazioni sono altrettanto chiare sul runner: "I self-hosted runner non dovrebbero quasi mai essere usati per repository pubblici su GitHub, perché qualsiasi utente può aprire pull request verso il repository e compromettere l'ambiente."

Questo comporta due scelte progettuali. Il job include una condizione che ne consente l'esecuzione solo sui branch inviati al tuo repository. Inoltre, il workflow non contiene alcuno step actions/checkout. L'agent non ha mai il branch sul disco, quindi una pull request ostile è soltanto testo inviato a un modello. Non può eseguire uno script di build sul tuo VPS, perché sul tuo VPS non viene mai eseguito nulla che la riguardi. Tuttavia, il testo non è automaticamente innocuo: un diff scritto da uno sconosciuto è input non attendibile che arriva a un modello, lo stesso confine di attendibilità che incontri quando concedi a un agent di effettuare ricerche sul Web, e qui l'unico contenimento consiste nel fatto che questo agent può soltanto pubblicare un commento.

Scarica il diff, non il repository

Una richiesta restituisce l'intero diff come testo semplice.

export GH_TOKEN=your_token   # in the workflow this comes from secrets.GITHUB_TOKEN
gh api /repos/OWNER/REPO/pulls/42 -H "Accept: application/vnd.github.diff"

Il tipo di contenuto Accept: application/vnd.github.diff trasforma la risposta da un oggetto JSON che descrive la pull request nel diff unificato vero e proprio, mentre gh api stampa il corpo senza modificarlo. La prima riga visualizzata dovrebbe iniziare con diff --git a/. Un gh: Not Found (HTTP 404) indica che il token non può accedere al repository; con un personal token a granularità fine, nella quasi totalità dei casi significa che non è stata abilitata l'autorizzazione Pull requests.

Filtra prima di consumare un token

Questa sezione determina se le persone leggono i messaggi del bot oppure lo silenziano. Ogni filtro seguente viene applicato prima che il modello riceva anche un solo byte.

  • Filtri sui percorsi. Escludi i file di lock, le directory con dipendenze incluse nel repository, i bundle minificati e il codice generato. Un commento del modello su package-lock.json è solo rumore, e questi file spesso costituiscono la maggior parte dei byte in una diff.
  • Un limite di dimensione. Oltre il limite, salta la revisione e termina con esito positivo. Per un refactoring di 4,000 righe è preferibile un messaggio chiaro che indichi che le dimensioni impedivano la revisione automatica, invece di sessanta ipotesi.
  • Una soglia di gravità e un limite al numero di commenti. Segnala i problemi di gravità alta e media, fino a un massimo di dieci, in ordine di gravità decrescente. Nessuno legge l'undicesimo commento.

Lo script

Salvare questo contenuto come /opt/pr-review/review.py. Il contenuto legge la configurazione dall'ambiente, quindi il workflow può cambiare modello senza modificare il codice.

#!/usr/bin/env python3
"""Review only the changed lines of one pull request."""
import json
import os
import subprocess
import sys

import anthropic

REPO = os.environ["GITHUB_REPOSITORY"]
PR = os.environ["PR_NUMBER"]
MODEL = os.environ.get("REVIEW_MODEL", "claude-haiku-4-5-20251001")
MAX_DIFF_BYTES = int(os.environ.get("MAX_DIFF_BYTES", "120000"))
MIN_SEVERITY = os.environ.get("MIN_SEVERITY", "medium")
MAX_COMMENTS = 10
RANK = {"low": 0, "medium": 1, "high": 2}
SKIP = ("package-lock.json", "poetry.lock", "/vendor/", "/node_modules/", ".min.js")

raw_diff = subprocess.run(
    ["gh", "api", f"/repos/{REPO}/pulls/{PR}",
     "-H", "Accept: application/vnd.github.diff"],
    check=True, capture_output=True, text=True,
).stdout

La suddivisione del diff per file rende possibile il filtraggio dei percorsi. La numerazione di ogni riga consente di posizionare correttamente i commenti della revisione. GitHub accetta un commento inline solo su una riga inclusa nel diff, quindi il modello deve indicare un numero di riga reale. Fornendogli i numeri, può copiarne uno invece di inventarlo.

def per_file(diff_text):
    """Split a unified diff into one string per file."""
    sections, current = [], []
    for line in diff_text.splitlines():
        if line.startswith("diff --git ") and current:
            sections.append("\n".join(current))
            current = []
        current.append(line)
    if current:
        sections.append("\n".join(current))
    return sections


def annotate(section):
    """Prefix every line that exists in the new file with its line number."""
    out, n, in_hunk = [], 0, False
    for line in section.splitlines():
        if line.startswith("@@"):
            n = int(line.split("+")[1].split(",")[0].split(" ")[0])
            in_hunk = True
            out.append(line)
        elif not in_hunk or line.startswith(("-", "\\")):
            out.append(line)
        else:
            out.append(f"{n}\t{line}")
            n += 1
    return "\n".join(out)


kept = [s for s in per_file(raw_diff)
        if not any(p in s.split("\n", 1)[0] for p in SKIP)]
payload = "\n".join(annotate(s) for s in kept)

if not payload.strip():
    print("every changed file was filtered out")
    raise SystemExit(0)
if len(payload) > MAX_DIFF_BYTES:
    print(f"diff is {len(payload)} bytes, over the {MAX_DIFF_BYTES} cap")
    raise SystemExit(0)

L'intestazione dell'hunk contiene la numerazione. @@ -12,7 +12,9 @@ indica che l'hunk del nuovo file inizia alla riga 12, quindi il contatore parte da quel valore e avanza solo sulle righe aggiunte e invariate. Le righe rimosse vengono ignorate nella numerazione perché non esistono nel nuovo file. Il controllo sulle righe che iniziano con una barra rovesciata esclude il marker di assenza del newline che git scrive alla fine di un file. In caso contrario, tutti i numeri successivi verrebbero spostati di uno.

Entrambe le uscite usano lo stato 0, non 1. Una pull request filtrata o troppo grande deve mostrare un controllo verde. Un controllo rosso su cui una persona non può intervenire viene ignorato; quando un controllo viene ignorato, lo stesso vale per tutti gli altri.

SYSTEM = (
    "You review one pull request diff. Every line that exists in the new file is "
    "prefixed with its line number and a tab character. "
    "Report only defects you can see in the lines shown: a crash, a resource leak, "
    "a security mistake, a wrong boundary condition, a broken contract with code "
    "that is visible in this diff. Do not comment on style, naming or formatting. "
    "Do not guess about code you cannot see. Leave out anything you are not "
    "certain about. An empty findings list is a normal and common answer. "
    'Reply with JSON only, in this shape: {"findings": [{"path": "src/app.py", '
    '"line": 42, "severity": "high", "comment": "what is wrong, then why"}]} '
    "Every line number must be one you can see in the left column of that file."
)

client = anthropic.Anthropic()
message = client.messages.create(
    model=MODEL,
    max_tokens=2000,
    system=SYSTEM,
    messages=[{"role": "user", "content": payload}],
)
print(f"stop={message.stop_reason} in={message.usage.input_tokens} "
      f"out={message.usage.output_tokens}", file=sys.stderr)

text = message.content[0].text
findings = json.loads(text[text.find("{"):text.rfind("}") + 1])["findings"]
findings = [f for f in findings if RANK.get(f["severity"], 0) >= RANK[MIN_SEVERITY]]
findings.sort(key=lambda f: -RANK.get(f["severity"], 0))
del findings[MAX_COMMENTS:]

if not findings:
    print("nothing above the severity threshold; posting no comment")
    raise SystemExit(0)

review = {
    "event": "COMMENT",
    "body": f"Automated review of the changed lines. {len(findings)} finding(s).",
    "comments": [
        {"path": f["path"].removeprefix("b/"), "line": f["line"], "side": "RIGHT",
         "body": f"**{f['severity']}** {f['comment']}"}
        for f in findings
    ],
}
subprocess.run(
    ["gh", "api", "-X", "POST", f"/repos/{REPO}/pulls/{PR}/reviews", "--input", "-"],
    input=json.dumps(review), text=True, check=True,
)

In quel blocco, tre dettagli sono essenziali. Il JSON viene estratto tra il primo { e l'ultimo } perché talvolta un modello racchiude la risposta in un code fence e json.loads non gestisce correttamente quel delimitatore. Il path viene privato di un eventuale b/ iniziale, perché quel prefisso proviene dall'intestazione del diff e GitHub richiede un percorso relativo alla repository. Infine, --input - invia l'intera revisione con una sola chiamata API, quindi dieci risultati arrivano come una notifica invece di dieci.

Quando non ci sono elementi da segnalare, lo script non pubblica nulla. Un bot che scrive "nessun problema rilevato" su ogni pull request abitua le persone a ignorare quel messaggio. Di conseguenza, rischiano di ignorare anche quello che conta.

Collegalo al flusso di lavoro

Salva questo contenuto come .github/workflows/pr-review.yml:

name: pr-review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths-ignore:
      - '**.md'
      - 'docs/**'

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: pr-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    if: github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.pull_request.labels.*.name, 'no-ai-review')
    runs-on: [self-hosted, linux, pr-review]
    steps:
      - name: Review the changed lines
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REVIEW_MODEL: claude-haiku-4-5-20251001
          MIN_SEVERITY: medium
        run: /opt/pr-review/venv/bin/python /opt/pr-review/review.py

GITHUB_REPOSITORY non si trova nel blocco env: perché Actions lo imposta già per ogni job. Il gruppo concurrency è importante per i costi: senza questa impostazione, l'invio di tre correzioni rapide a un branch avvia tre revisioni complete e tutte e tre vengono conteggiate. Con questa impostazione, rimane solo l'ultima.

La riga if: svolge due funzioni. La prima parte esclude le pull request provenienti da fork, che fallirebbero comunque perché non dispongono della chiave. La seconda offre al team un interruttore per disattivare il job: aggiungi l'etichetta no-ai-review a una pull request e il job non verrà eseguito.

Apri una pull request e osserva il risultato:

gh run list --workflow=pr-review.yml --limit 3
gh run view --log
gh pr view 42 --comments

Un'esecuzione che termina in pochi secondi e mostra nothing above the severity threshold; posting no comment nel log funziona correttamente. Questo è il risultato previsto per una pull request piccola e senza problemi.

Quanto costa una revisione automatizzata delle pull request?

Il diff costituisce quasi tutto l’input, quindi la sua dimensione determina il prezzo. Di seguito è riportato un diff misurato di 500 righe insieme al prompt di sistema. Il conteggio è stato ottenuto con l’endpoint di conteggio dei token, non tramite una stima.

ChartTokens for one 500 line pull request diff, measured August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "input_tokens": "8,000",
    "output_tokens": "1,200"
  },
  {
    "label": "Sonnet 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  },
  {
    "label": "Opus 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  }
]

Quel diff corrispondeva a 8,000 token di input su Haiku 4.5 e a 10,400 su Sonnet 5. Il testo è identico, ma il conteggio è diverso. I modelli Claude dalla versione 4.7 in poi usano un tokenizer più recente, che produce circa il 30% di token in più a parità di input. Anthropic lo documenta nella pagina dei prezzi. Tenetene conto quando confrontate il costo per un milione di token di un modello più recente con quello di un modello precedente.

Prezzi di listino ad agosto 2026: Haiku 4.5 costa $1 per un milione di token di input e $5 per un milione di token di output. Sonnet 5 costa $2 e $10 con il prezzo introduttivo valido fino al 31 agosto 2026; in seguito costerà $3 e $15. Opus 5 costa $5 e $25.

ChartCost of that one review at list prices, August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "cost_per_pr_cents": 1.4,
    "cost_200_prs_usd": "2.80"
  },
  {
    "label": "Sonnet 5",
    "cost_per_pr_cents": 3.64,
    "cost_200_prs_usd": "7.28"
  },
  {
    "label": "Opus 5",
    "cost_per_pr_cents": 9.1,
    "cost_200_prs_usd": "18.20"
  }
]

Il costo è di 1.4 centesimi per pull request con Haiku 4.5 e di 9.1 centesimi con Opus 5. Un team che integra 200 pull request al mese paga circa $2.80 con Haiku 4.5, $7.28 con Sonnet 5 oppure $18.20 con Opus 5. Dal 1 settembre 2026, moltiplicate per 1.5 il valore della riga relativa a Sonnet 5.

Due fattori possono portare il costo effettivo oltre questa stima. Il trigger synchronize avvia una revisione a ogni push, quindi un branch attivo con otto push costa otto revisioni. La regola sulla concorrenza è utile solo quando i push arrivano a breve distanza l’uno dall’altro. Le cifre presuppongono inoltre che i filtri sui percorsi funzionino: un solo file di lock non filtrato può raddoppiare da solo la quantità di input.

Il prompt caching non è utile in questo caso. Il prefisso memorizzato nella cache deve essere identico a livello di byte tra una chiamata e l’altra, mentre il diff cambia ogni volta. Il prompt di sistema è l’unica parte stabile, ma è molto più breve della lunghezza minima richiesta per la memorizzazione nella cache. Per la regola generale, vedere quando il prompt caching diventa conveniente; per scegliere tra i tre modelli indicati sopra, vedere quale modello Claude usare per ogni attività.

Misurate i vostri diff prima di attivarlo

Aggiungete questa riga dopo la creazione di payload, quindi eseguite manualmente lo script su alcune pull request del mese scorso:

print(client.messages.count_tokens(
    model=MODEL, system=SYSTEM, messages=[{"role": "user", "content": payload}]
).input_tokens)

L’endpoint di conteggio non esegue il modello, quindi non consuma token di input o di output e usa il tokenizer associato al modello specificato. Eseguite il conteggio su dieci pull request reali del vostro repository e calcolate la mediana invece della media, in modo che una migrazione molto grande non alteri la stima.

Perché i bot di revisione vengono disattivati e come evitarlo

Due comportamenti distruggono la fiducia in questi bot, ed entrambi hanno una correzione nel codice precedente.

Revisionare tutto in una volta. Un bot che lascia quaranta commenti non ottiene l'attenzione di nessuno. La soglia di gravità e il limite di dieci commenti non sono una formalità: servono a mantenere visibili i problemi reali. Ordinare i risultati per gravità prima di applicare il limite fa sì che vengano eliminati i problemi meno importanti, anziché dieci risultati casuali.

Commentare con sicurezza su qualcosa che non può verificare. È questo il comportamento che porta gli ingegneri a disattivare definitivamente il bot. Se riceve 200 righe di una codebase di 40,000 righe, un modello scriverà comunque «questo interrompe l'invalidazione della cache in redis_client.py» riferendosi a un file che non ha mai visto. Il prompt di sistema lo impedisce con istruzioni chiare: segnalare solo i difetti visibili nelle righe mostrate e omettere tutto ciò di cui non si è certi. Indicare direttamente il tipo di errore funziona meglio che chiedere genericamente accuratezza. Specificare inoltre che un risultato vuoto è normale impedisce al modello di inventare un problema da segnalare per una modifica di due righe.

Pubblica la revisione come COMMENT, mai come REQUEST_CHANGES. L'opinione di un modello non deve poter bloccare un merge. Non appena questo diventa possibile, qualcuno con una scadenza imminente rimuoverà l'intero workflow invece di discuterne.

Modalità di errore e stringhe visualizzate

HTTP 422 durante l'invio della review. gh stampa gh: Unprocessable Entity (HTTP 422) e il corpo della risposta indica il campo: Pull request review thread line must be part of the diff. GitHub non riesce ad ancorare quel commento. Le cause più comuni sono un numero di riga inventato dal modello, un path che contiene ancora il prefisso b/ oppure un commento su una riga rimossa, per il quale è necessario impostare side su LEFT invece che su RIGHT. Stampare il JSON della review prima dell'invio e verificare manualmente un commento rispetto al diff.

invalid x-api-key dall'API del modello. Il passaggio non riesce alla prima chiamata messages.create. Il secret ANTHROPIC_API_KEY non è impostato nel repository oppure la pull request proviene da un fork e Actions non ha passato alcun secret. Il controllo sui fork nella riga if: avrebbe dovuto saltare il passaggio; verificare quindi prima quella riga.

gh: Resource not accessible by integration (HTTP 403). Il token del job non può scrivere nelle pull request. Aggiungere pull-requests: write al blocco permissions:. Se è già presente, controllare Settings, quindi Actions e infine General, dove una policy dell'organizzazione può limitare ciò che qualsiasi token del workflow può richiedere.

json.decoder.JSONDecodeError. Il modello non ha restituito JSON analizzabile. La causa più comune è una risposta che ha raggiunto il limite di token e si è interrotta a metà oggetto. La riga di log stampa stop_reason proprio per questo: un valore di max_tokens indica che occorre aumentare max_tokens o ridurre MAX_COMMENTS.

Il workflow non viene mai eseguito. gh run list non mostra nulla per la pull request. Verificare che paths-ignore non abbia escluso tutti i file modificati, quindi il controllo sui fork e quello sull'etichetta. Verificare infine che il runner sia attivo sul VPS tramite sudo systemctl status 'actions.runner.*'. Un runner offline lascia il job in coda senza messaggi di errore nella pull request.

Ogni review è vuota. Impostare MIN_SEVERITY su low per un'esecuzione. Se compaiono segnalazioni, la soglia funziona correttamente. Se non compare nulla, stampare payload e verificare che i filtri non abbiano escluso l'intero diff.

Eseguirlo insieme agli altri agenti

Il reviewer è di piccole dimensioni, quindi può essere allettante installarlo sul server che esegue già tutto il resto. Se il repository è importante, mantienilo separato. Questo processo dispone di un token che può commentare il codice e di una chiave che può spendere il tuo denaro; inoltre, un self-hosted runner è progettato per eseguire il codice dei workflow. Un account dedicato senza privilegi sudo, su un host che non esegue altro, è il requisito di base. Se esegui anche agenti interattivi che fanno il checkout del codice, una VM temporanea per ogni agente è il modello più affidabile; per la configurazione generale, consulta eseguire un coding agent su un VPS. Se non conosci ancora l'API Anthropic, una prima applicazione con Claude API su un VPS offre un punto di partenza più semplice.

FAQ

Un agente AI per la revisione delle PR deve avere accesso in scrittura al mio repository?

No. Gli servono pull-requests: write per pubblicare una revisione e contents: read per recuperare il diff. Questo è l'elenco completo. Imposti questi permessi nel blocco permissions: del workflow, che limita ciò che il GITHUB_TOKEN del singolo job può fare. Con queste due righe l'agente può commentare una pull request, ma non può eseguire il push di un commit né effettuare il merge di un branch. Pubblica le revisioni con event: COMMENT invece di REQUEST_CHANGES, così l'agente non può nemmeno bloccare un merge.

Perché il mio commento di revisione fallisce con HTTP 422?

GitHub accetta un commento inline della revisione solo su una riga inclusa nel diff della pull request. In caso contrario restituisce Pull request review thread line must be part of the diff. Verifica che path sia relativo alla repository e non includa il prefisso b/ presente nell'intestazione del diff. Verifica inoltre che il numero di riga si trovi all'interno di un hunk relativo a quel file. side deve essere RIGHT per una riga aggiunta o invariata e LEFT per una riga rimossa. Anteporre a ogni riga del diff il numero di riga del nuovo file prima di inviare il diff al modello impedisce al modello di inventare numeri di riga.

Posso eseguire questa procedura su una repository pubblica con pull request provenienti da fork?

Non con questo design. GitHub non passa i secret a un workflow attivato da un fork, quindi la chiave del modello non è disponibile e l'esecuzione fallisce. GitHub specifica inoltre che i runner self-hosted «dovrebbero essere usati quasi mai per le repository pubbliche», perché chiunque può aprire una pull request che causa l'esecuzione di codice sulla macchina. Per un progetto pubblico, limita la revisione ai branch inviati direttamente alla repository, come fa il controllo if:, oppure sposta il passaggio di revisione su un runner gestito da GitHub e accetta che il diff lasci la tua infrastruttura.

Quale modello devo usare per la revisione delle pull request?

Inizia con Haiku 4.5. Analizzare un diff con dimensioni limitate rispetto a un elenco fisso di tipi di difetti non richiede un ragionamento complesso. Il modello meno costoso mantiene la spesa mensile a un livello facilmente accettabile. Passa a Sonnet 5 se rilevi bug reali nel tuo linguaggio o framework e misura il miglioramento invece di darlo per scontato. Opus 5 è di gran lunga il più costoso dei tre per pull request. È quindi più facile giustificarne l'uso su un branch di release che a ogni push su ogni branch di funzionalità.