SSD Nodes Learn 🎉 VPS dès $4.99/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-07

Héberger un agent IA de revue de PR sur un VPS

Installez un reviewer IA sur votre VPS avec runner self-hosted, prompts limités au diff, filtres de taille, commentaires inline et coût exact par PR.

Ce que fait un agent de revue de PR auto-hébergé

Un agent de revue de PR auto-hébergé est un petit programme installé sur un serveur que vous contrôlez. Il lit le diff d’une pull request (PR) et envoie uniquement les lignes modifiées à un modèle. La réponse est publiée sous forme de commentaires inline dans la revue. Il ne checkout jamais votre branche et ne lit jamais un fichier que la pull request n’a pas modifié. Il ne détient que deux identifiants : une clé d’API (interface de programmation d’application) du modèle et un token autorisé à ajouter des commentaires, sans autre permission.

Un modèle sait lire un diff. Cette partie est résolue. L’essentiel est de savoir où va le diff et qui détient la clé. Avec un bot de revue hébergé, chaque diff provenant de chaque dépôt privé quitte votre réseau, est enregistré dans les logs d’un tiers et reste soumis à sa politique de conservation. Sur un VPS (serveur privé virtuel) que vous contrôlez, le diff va de GitHub vers votre serveur, puis vers l’API du modèle. Vous pouvez lire les quarante lignes de code qui déterminent ce qui est envoyé.

Ce qu’il vous faut avant de commencer

  • Un VPS sous Ubuntu 24.04 avec un runner GitHub Actions auto-hébergé déjà enregistré auprès du dépôt. Ajoutez-lui le label supplémentaire pr-review lors de son enregistrement, car le workflow ci-dessous sélectionne ce label.
  • Une clé API Anthropic obtenue depuis la Claude Console.
  • Un dépôt dans lequel vous contrôlez les personnes autorisées à ouvrir une pull request. Un dépôt privé est le cas le plus simple. La section consacrée aux forks traite du cas public, mais la réponse y est moins satisfaisante.

Installer le reviewer sur le VPS

Le service runner s’exécute avec le compte sans privilèges que vous avez créé en exécutant ./svc.sh install. Installez le reviewer avec ce même compte afin que le job puisse l’exécuter sans sudo. Remplacez runner ci-dessous par le nom de votre compte.

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 affiche gh version 2.45.0 sur Ubuntu 24.04 en août 2026. Toute version à partir de 2.20 inclut le flag --input utilisé ci-dessous. Un message Command 'gh' not found signifie que le composant universe n’est pas activé. Exécutez donc sudo add-apt-repository universe, puis réessayez.

Où se trouvent la clé et le token

Deux secrets, deux durées de vie différentes. Aucun des deux ne doit être ajouté au dépôt.

ANTHROPIC_API_KEY est un secret de dépôt. Vous le définissez dans Settings, puis Secrets and variables, puis Actions. GitHub le chiffre et l’injecte dans l’environnement de l’étape au moment de l’exécution. Il n’est jamais écrit dans un fichier sur le disque et n’apparaît jamais dans l’historique git.

GITHUB_TOKEN fonctionne différemment. Actions génère un token pour chaque job et le détruit à la fin du job. Les opérations autorisées avec ce token sont définies par le bloc permissions: du workflow. C’est donc ici que le principe du moindre privilège s’applique réellement :

permissions:
  contents: read
  pull-requests: write

Ce token peut publier une review. Il ne peut pas pousser un commit, fusionner une branche, modifier un fichier de workflow ni accéder à un autre dépôt. Un agent qui peut ajouter des commentaires est un reviewer. Un agent qui peut pousser est un committer, et personne n’a autorisé cela. Traitez la clé du modèle avec la même rigueur, car elle peut engager des dépenses sur votre compte. Vous trouverez d’autres informations sur ce type de problème dans garder les secrets hors de portée d’un agent IA.

Actions remplace la chaîne exacte du secret par *** dans les journaux des jobs. La correspondance porte uniquement sur la chaîne exacte. Une clé encodée en base64, répartie sur deux lignes ou affichée un caractère à la fois apparaît donc en clair. N’ajoutez pas d’étape de débogage qui affiche le contenu de l’environnement.

Pourquoi une pull request provenant d’un fork ne voit jamais votre clé d’API

La règle de GitHub est simple : à l’exception de GITHUB_TOKEN, les secrets ne sont pas transmis au runner lorsqu’un workflow est déclenché depuis un dépôt forké. Ainsi, une exécution pull_request depuis un fork lance votre script sans ANTHROPIC_API_KEY, et le premier appel d’API échoue avec invalid x-api-key.

La correction la plus tentante consiste à remplacer le déclencheur par pull_request_target, qui s’exécute dans le contexte du dépôt de base et reçoit bien les secrets. Ne faites pas cela ici. La documentation de sécurité de GitHub précise que ces workflows « sont privilégiés, ce qui signifie qu’ils partagent le même cache de la branche principale avec les autres déclencheurs de workflows privilégiés, et peuvent avoir un accès en écriture au dépôt ainsi qu’aux secrets référencés », et que le résultat « peut être exploité pour prendre le contrôle d’un dépôt ».

Cette même documentation est tout aussi claire au sujet du runner : « Les self-hosted runners ne doivent presque jamais être utilisés pour des dépôts publics sur GitHub, car n’importe quel utilisateur peut ouvrir des pull requests vers le dépôt et compromettre l’environnement. »

Cela impose deux choix d’architecture. Le job contient une condition qui l’autorise uniquement à s’exécuter sur les branches poussées vers votre propre dépôt. Et le workflow ne contient aucune étape actions/checkout. L’agent n’a jamais la branche sur le disque : une pull request malveillante est donc uniquement du texte transmis à un modèle. Elle ne peut pas exécuter de script de build sur votre VPS, car rien sur votre VPS ne l’exécute.

Récupérer le diff, pas le dépôt

Une seule requête récupère l’intégralité du diff au format texte brut.

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"

Le type MIME Accept: application/vnd.github.diff transforme la réponse, qui est normalement un objet JSON décrivant la pull request, en diff unifié. gh api affiche ensuite ce corps sans le modifier. La première ligne affichée doit commencer par diff --git a/. Une réponse gh: Not Found (HTTP 404) signifie que le token n’a pas accès au dépôt. Avec un personal access token à granularité fine, cela signifie presque toujours que l’autorisation Pull requests n’a pas été activée.

Filtrez avant de dépenser un token

Cette section fait la différence entre un bot que les utilisateurs lisent et un bot qu’ils réduisent au silence. Chaque filtre ci-dessous s’exécute avant que le modèle ne voie le moindre octet.

  • Filtres de chemin. Excluez les fichiers de verrouillage, les répertoires de dépendances incluses, les bundles minifiés et le code généré. Un commentaire du modèle sur package-lock.json n’apporte aucune information utile, et ces fichiers représentent souvent la majeure partie des octets d’un diff.
  • Une limite de taille. Au-delà de cette limite, ignorez la revue et quittez avec un statut de succès. Un refactoring de 4,000 lignes produit ainsi une phrase honnête indiquant qu’il était trop volumineux pour être examiné automatiquement, au lieu de soixante suppositions.
  • Un seuil de gravité et une limite de commentaires. Signalez les problèmes de gravité élevée et moyenne, dans la limite de dix, en commençant par la gravité la plus élevée. Personne ne lit le commentaire numéro onze.

Le script

Enregistrez-le sous /opt/pr-review/review.py. Il lit sa configuration depuis l’environnement. Le workflow peut donc changer de modèle sans modifier le code.

#!/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

Le découpage du diff par fichier permet le filtrage des chemins. La numérotation de chaque ligne permet de positionner les commentaires de revue. GitHub n’accepte un commentaire inline que sur une ligne présente dans le diff. Le modèle doit donc citer un numéro de ligne réel. Lui fournir ces numéros lui permet d’en recopier un au lieu d’en inventer un.

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’en-tête du hunk contient la numérotation. @@ -12,7 +12,9 @@ indique que le hunk du nouveau fichier commence à la ligne 12. Le compteur démarre donc à cette valeur et avance uniquement pour les lignes ajoutées ou inchangées. Les lignes supprimées restent sans numéro, car elles n’existent pas dans le nouveau fichier. La condition qui ignore les lignes commençant par une barre oblique inverse évite de traiter le marqueur de fin de fichier sans retour à la ligne écrit par git. Sans cette condition, tous les numéros suivants seraient décalés d’une unité.

Les deux sorties utilisent le code 0, et non 1. Une pull request filtrée ou trop volumineuse doit afficher une vérification réussie. Une vérification en échec sur laquelle personne ne peut agir est ignorée. Dès qu’une vérification est ignorée, elles le sont toutes.

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,
)

Trois détails de ce bloc sont indispensables. Le JSON est extrait entre le premier { et le dernier }, car un modèle peut parfois entourer sa réponse d’un code fence, et json.loads ne sait pas traiter ce fence. Le path est débarrassé d’un éventuel b/ initial, car ce préfixe provient de l’en-tête du diff et GitHub attend un chemin relatif au dépôt. Enfin, --input - envoie toute la revue en un seul appel d’API. Les dix résultats arrivent ainsi dans une seule notification, et non dans dix notifications.

Lorsqu’il n’y a rien à signaler, le script n’envoie rien. Un bot qui écrit « aucun problème trouvé » sur chaque pull request apprend aux utilisateurs à ne plus le lire. Ils risquent alors de ne pas voir celui qui compte.

Intégrez-le au workflow

Enregistrez ceci dans .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 ne figure pas dans ce bloc env:, car Actions le définit déjà pour chaque job. Le groupe concurrency est important pour la facturation : sans lui, trois corrections rapides poussées sur une branche déclenchent trois reviews complètes, et vous payez les trois. Avec lui, seule la dernière est conservée.

La ligne if: remplit deux fonctions. La première moitié ignore les pull requests provenant de forks, qui échoueraient de toute façon sans clé. La seconde fournit à votre équipe un mécanisme de désactivation : ajoutez le label no-ai-review à une pull request pour empêcher l’exécution du job.

Ouvrez une pull request et observez le résultat :

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

Une exécution qui se termine en quelques secondes et dont le journal contient nothing above the severity threshold; posting no comment fonctionne correctement. Pour une pull request de petite taille et propre, c’est le résultat attendu.

Quel est le coût d’une revue automatisée de pull request ?

Le diff constitue presque la totalité de l’entrée. Sa taille détermine donc le prix. Voici un diff mesuré de 500 lignes, accompagné du system prompt. Le calcul utilise l’endpoint de comptage des tokens, et non une estimation.

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"
  }
]

Ce diff représente 8,000 tokens d’entrée avec Haiku 4.5 et 10,400 avec Sonnet 5. Le texte est identique, mais le nombre de tokens diffère. Les modèles Claude à partir de 4.7 utilisent un tokenizer plus récent, qui produit environ 30 % de tokens supplémentaires pour la même entrée. Anthropic le précise sur sa page tarifaire. Tenez-en compte lorsque vous comparez le prix par million de tokens d’un modèle récent avec celui d’un modèle plus ancien.

Tarifs catalogue en août 2026 : Haiku 4.5 coûte $1 par million de tokens d’entrée et $5 par million de tokens de sortie. Sonnet 5 coûte $2 et $10 dans le cadre du tarif de lancement valable jusqu’au 31 août 2026, puis $3 et $15. Opus 5 coûte $5 et $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"
  }
]

Le coût est de 1.4 cents par pull request avec Haiku 4.5 et de 9.1 cents avec Opus 5. Une équipe qui fusionne 200 pull requests par mois paie environ $2.80 avec Haiku 4.5, $7.28 avec Sonnet 5 ou $18.20 avec Opus 5. À partir du 1 septembre 2026, multipliez le montant de la ligne Sonnet 5 par 1.5.

Deux éléments peuvent augmenter la facture réelle par rapport à cette estimation. Le trigger synchronize lance une revue à chaque push. Une branche active avec huit push coûte donc huit revues. La règle de concurrence n’est utile que lorsque les push arrivent presque simultanément. Ces chiffres supposent également que les path filters fonctionnent : un seul lock file non filtré peut à lui seul doubler la taille de l’entrée.

Le prompt caching n’est pas utile ici. Le préfixe mis en cache doit être identique octet par octet entre les appels, alors que le diff change à chaque fois. Le system prompt est la seule partie stable, et il est bien plus court que la longueur minimale requise pour la mise en cache. Pour la règle générale, consultez dans quels cas le prompt caching est rentable, et pour choisir entre les trois modèles ci-dessus, quel modèle Claude utiliser selon la tâche.

Mesurez vos propres diffs avant de l’activer

Ajoutez cette ligne après la création de payload, puis exécutez le script manuellement sur quelques pull requests du mois dernier :

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

L’endpoint de comptage n’exécute pas le modèle. Il ne consomme donc aucun token d’entrée ou de sortie et utilise le tokenizer correspondant au modèle indiqué. Exécutez-le sur dix pull requests réelles de votre propre dépôt et utilisez la médiane plutôt que la moyenne, afin qu’une migration volumineuse ne fausse pas l’estimation.

Pourquoi les bots de review sont désactivés, et comment l’éviter

Deux comportements détruisent la confiance dans ces bots. Le code ci-dessus corrige les deux.

Tout reviewer en une seule fois. Un bot qui laisse quarante commentaires n’en voit aucun lu. Le seuil de sévérité et la limite de dix commentaires ne relèvent pas de la politesse. Ils permettent de garder les problèmes réels visibles. Trier par sévérité avant de tronquer la liste garantit que la limite supprime les problèmes les moins importants, et non dix problèmes choisis au hasard.

Affirmer quelque chose que le bot ne peut pas vérifier. C’est ce qui pousse les ingénieurs à le désactiver définitivement. Si on lui montre 200 lignes d’une base de code de 40,000 lignes, un modèle écrira quand même « cela casse l’invalidation du cache dans redis_client.py » à propos d’un fichier qu’il n’a jamais vu. Le system prompt le précise clairement : signalez uniquement les défauts visibles dans les lignes affichées et omettez tout ce dont vous n’êtes pas certain. Nommer directement le risque fonctionne mieux que de demander une précision générale. Indiquer au modèle qu’un résultat vide est normal l’empêche d’inventer un problème à signaler pour une modification de deux lignes.

Publiez la review comme COMMENT, jamais comme REQUEST_CHANGES. L’avis d’un modèle ne doit pas pouvoir bloquer une merge. Dès que c’est possible, une personne pressée supprimera tout le workflow plutôt que de devoir le contester.

Modes d’échec et chaînes affichées

HTTP 422 lors de l’envoi de la review. gh affiche gh: Unprocessable Entity (HTTP 422) et le corps de la réponse indique le champ concerné : Pull request review thread line must be part of the diff. GitHub ne peut pas ancrer ce commentaire. Les causes habituelles sont un numéro de ligne inventé par le modèle, un path qui contient encore le préfixe b/, ou un commentaire placé sur une ligne supprimée, pour lequel il faut définir side sur LEFT au lieu de RIGHT. Affichez le JSON de la review avant l’envoi et vérifiez manuellement un commentaire par rapport au diff.

invalid x-api-key depuis l’API du modèle. L’étape échoue lors du premier appel messages.create. Soit le secret ANTHROPIC_API_KEY n’est pas défini dans le repository, soit la pull request provient d’un fork et Actions n’a transmis aucun secret. La protection contre les forks dans la ligne if: aurait dû ignorer cette étape. Vérifiez donc d’abord cette ligne.

gh: Resource not accessible by integration (HTTP 403). Le token du job ne peut pas écrire dans les pull requests. Ajoutez pull-requests: write au bloc permissions:. Si cette permission y figure déjà, ouvrez Settings, puis Actions, puis General. Une policy de l’organisation peut limiter les permissions que n’importe quel token de workflow est autorisé à demander.

json.decoder.JSONDecodeError. Le modèle n’a pas renvoyé de JSON analysable. La cause la plus courante est une réponse qui a atteint la limite de tokens et s’est interrompue au milieu d’un objet. La ligne de log affiche stop_reason à cet effet : une valeur de max_tokens signifie qu’il faut augmenter max_tokens ou réduire MAX_COMMENTS.

Le workflow ne s’exécute jamais. gh run list n’affiche rien pour la pull request. Vérifiez que paths-ignore n’a pas filtré tous les fichiers modifiés, puis contrôlez la protection contre les forks et celle liée au label. Vérifiez enfin que le runner est actif avec sudo systemctl status 'actions.runner.*' sur le VPS. Un runner hors ligne laisse le job en attente, sans message d’erreur dans la pull request.

Toutes les reviews reviennent vides. Définissez MIN_SEVERITY sur low pour une exécution. Si des résultats apparaissent, le seuil fonctionne comme prévu. Si rien n’apparaît, affichez payload et vérifiez que les filtres n’ont pas supprimé l’intégralité du diff.

Exécuter cet outil avec vos autres agents

Le reviewer est léger, et il peut être tentant de l’installer sur le serveur qui exécute déjà tout le reste. Séparez-le si le dépôt est important. Ce processus détient un token qui peut commenter votre code et une key qui dépense votre argent. De plus, un self-hosted runner est par conception un environnement où du code de workflow s’exécute. Un compte dédié sans privilèges et sans droits sudo, sur un hôte qui n’exécute rien d’autre, constitue le minimum. Si vous exécutez également des agents interactifs qui font un checkout du code, une VM éphémère par agent est le modèle le plus fiable. Exécuter un agent de codage sur un VPS couvre la configuration générale. Si l’API Anthropic vous est inconnue, une première application utilisant l’API Claude sur un VPS constitue un point de départ plus simple.

FAQ

Un agent IA de revue des pull requests a-t-il besoin d’un accès en écriture à mon dépôt ?

Non. Il a besoin de pull-requests: write pour publier une revue et de contents: read pour récupérer le diff. C’est tout, et vous définissez ces permissions dans le bloc permissions: du workflow, qui limite ce que peut faire le GITHUB_TOKEN de chaque job. Avec ces deux lignes, l’agent peut commenter une pull request, mais ne peut ni pousser un commit ni fusionner une branche. Publiez les revues avec event: COMMENT plutôt qu’avec REQUEST_CHANGES afin qu’il ne puisse pas non plus bloquer une fusion.

Pourquoi mon commentaire de revue échoue-t-il avec HTTP 422 ?

GitHub accepte un commentaire de revue inline uniquement sur une ligne présente dans le diff de la pull request. Sinon, il renvoie Pull request review thread line must be part of the diff. Vérifiez que path est relatif au dépôt et ne contient pas le préfixe b/ provenant de l’en-tête du diff. Vérifiez aussi que le numéro de ligne se trouve dans un hunk de ce fichier. side doit être égal à RIGHT pour une ligne ajoutée ou inchangée, et à LEFT pour une ligne supprimée. Préfixer chaque ligne du diff avec son numéro de ligne dans le nouveau fichier avant de l’envoyer au modèle empêche justement le modèle d’inventer des numéros.

Puis-je exécuter cette configuration sur un dépôt public avec des pull requests provenant de forks ?

Non, pas avec cette conception. GitHub ne transmet pas les secrets à un workflow déclenché depuis un fork. La clé du modèle est donc absente et l’exécution échoue. GitHub indique également que les runners auto-hébergés « ne devraient presque jamais être utilisés pour des dépôts publics », car n’importe qui peut ouvrir une pull request qui déclenche l’exécution de code sur votre machine. Pour un projet public, limitez la revue aux branches poussées directement vers le dépôt, ce que fait la condition if:, ou déplacez l’étape de revue vers un runner hébergé par GitHub et acceptez que le diff quitte votre infrastructure.

Quel modèle dois-je utiliser pour la revue des pull requests ?

Commencez avec Haiku 4.5. Lire un diff limité et le comparer à une liste fixe de types de défauts ne demande pas un raisonnement complexe. Le modèle le moins cher maintient la facture mensuelle à un niveau raisonnable. Passez à Sonnet 5 si vous constatez qu’il ne détecte pas de vrais bugs dans votre langage ou votre framework, puis mesurez ce résultat au lieu de le supposer. Opus 5 est de loin le plus cher des trois pour chaque pull request. Il se justifie plus facilement sur une branche de release que sur chaque push vers chaque branche de fonctionnalité.