SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-13

Cara Host Sendiri Agen Review PR di VPS

Pelajari cara menjalankan agen review PR di VPS dengan runner mandiri, prompt berbasis diff, filter path dan ukuran, komentar inline, serta biaya per PR.

Agen peninjauan PR yang di-host sendiri

Agen peninjauan PR yang di-host sendiri adalah program kecil pada server milik Anda. Agen ini membaca diff pull request (PR) dan hanya mengirim baris yang berubah ke model. Hasilnya diposting sebagai komentar peninjauan inline. Agen ini tidak pernah melakukan checkout branch Anda dan tidak pernah membaca file yang tidak disentuh oleh pull request. Kredensial yang disimpannya hanya satu key API (application programming interface) model dan satu token yang hanya dapat menambahkan komentar.

Model dapat membaca diff. Bagian itu sudah teratasi. Yang penting adalah ke mana diff dikirim dan siapa yang memegang key tersebut. Bot peninjauan yang di-host berarti setiap diff dari setiap repositori privat keluar dari jaringan Anda, masuk ke log pihak ketiga, dan tunduk pada kebijakan retensi mereka. Pada VPS (virtual private server) milik Anda, diff mengalir dari GitHub ke server Anda lalu ke API model, dan Anda dapat membaca 40 baris kode yang menentukan data yang dikirim.

Hal yang Anda perlukan sebelum memulai

  • VPS yang menjalankan Ubuntu 24.04 dengan runner GitHub Actions yang di-host sendiri dan sudah terdaftar pada repositori. Tambahkan label pr-review saat mendaftarkannya, karena workflow di bawah memilih runner berdasarkan label tersebut.
  • API key Anthropic dari Claude Console.
  • Repositori yang memungkinkan Anda mengendalikan pihak yang dapat membuka pull request. Repositori privat adalah pilihan yang paling mudah. Bagian tentang fork di bawah membahas kasus publik, tetapi jawabannya kurang nyaman.

Instal reviewer pada VPS

Service runner berjalan menggunakan akun tanpa hak istimewa yang Anda buat saat menjalankan ./svc.sh install. Instal reviewer menggunakan akun yang sama agar job dapat menjalankannya tanpa sudo. Ganti runner di bawah dengan nama akun Anda.

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 menampilkan gh version 2.45.0 pada Ubuntu 24.04 per Agustus 2026. Semua rilis mulai dari 2.20 memiliki flag --input yang digunakan di bawah. Pesan Command 'gh' not found berarti komponen universe belum diaktifkan. Jalankan sudo add-apt-repository universe, lalu coba lagi.

Lokasi key dan token

Dua secret, dengan masa berlaku yang berbeda. Keduanya tidak boleh disimpan di repository.

ANTHROPIC_API_KEY adalah repository secret yang ditetapkan melalui Settings, lalu Secrets and variables, kemudian Actions. GitHub mengenkripsinya dan memasukkannya ke environment step saat runtime. Secret ini tidak pernah menjadi file di disk dan tidak pernah masuk ke riwayat git.

GITHUB_TOKEN bekerja secara berbeda. Actions membuat token baru untuk setiap job dan menghapusnya saat job berakhir. Tindakan yang dapat dilakukan token tersebut ditentukan oleh blok permissions: dalam workflow. Di sinilah prinsip least privilege benar-benar diterapkan:

permissions:
  contents: read
  pull-requests: write

Token tersebut dapat mengirim review. Token tersebut tidak dapat melakukan push commit, menggabungkan branch, mengedit file workflow, atau mengakses repository lain. Agent yang dapat menambahkan komentar berperan sebagai reviewer. Agent yang dapat melakukan push berperan sebagai committer, dan tidak seorang pun menyetujui kewenangan itu. Lindungi model key dengan tingkat kehati-hatian yang sama karena key tersebut dapat membelanjakan uang dari akun Anda. Penjelasan lebih lanjut tentang masalah ini tersedia di menjauhkan secret dari jangkauan AI agent.

Actions mengganti string secret yang persis sama dengan *** dalam log job. Pencocokan hanya dilakukan terhadap string yang persis sama. Karena itu, key yang Anda encode dengan base64, pecah menjadi dua baris, atau cetak satu karakter setiap kali akan terlihat dalam bentuk teks biasa. Jangan menambahkan step debug yang menampilkan seluruh environment.

Mengapa pull request dari fork tidak pernah menerima API key Anda

Aturan GitHub sederhana: kecuali GITHUB_TOKEN, secret tidak diteruskan ke runner ketika workflow dipicu dari repository hasil fork. Jadi, eksekusi pull_request dari fork memulai script Anda tanpa ANTHROPIC_API_KEY, dan panggilan API pertama gagal dengan invalid x-api-key.

Perbaikan yang terlihat mudah adalah mengganti trigger menjadi pull_request_target, yang berjalan dalam konteks repository dasar dan memang menerima secret. Jangan lakukan itu di sini. Panduan keamanan GitHub sendiri menyatakan bahwa workflow tersebut "memiliki hak istimewa, yang berarti workflow tersebut menggunakan cache branch utama yang sama dengan trigger workflow lain yang memiliki hak istimewa, dan mungkin memiliki akses tulis ke repository serta akses ke secret yang dirujuk", dan bahwa hasilnya "dapat dieksploitasi untuk mengambil alih repository".

Panduan yang sama juga memberikan peringatan tegas tentang runner: "Self-hosted runner hampir tidak boleh digunakan untuk repository publik di GitHub, karena siapa pun dapat membuka pull request ke repository tersebut dan membahayakan lingkungan."

Hal itu menghasilkan dua pilihan desain. Job memiliki guard sehingga hanya berjalan pada branch yang di-push ke repository milik Anda sendiri. Workflow juga sama sekali tidak memiliki langkah actions/checkout. Agent tidak pernah memiliki branch tersebut di disk, sehingga pull request berbahaya hanya berupa teks yang dikirim ke model. Agent tidak dapat menjalankan build script pada VPS Anda, karena tidak ada apa pun di VPS Anda yang pernah menjalankannya. Namun, teks tidak otomatis aman: diff yang ditulis orang lain adalah input tidak tepercaya yang diterima model, dengan batas kepercayaan yang sama seperti ketika Anda memberi agent akses pencarian web, dan satu-satunya pembatas di sini adalah agent tersebut tidak dapat melakukan apa pun selain memposting komentar.

Ambil diff, bukan repositori

Satu permintaan mengambil seluruh diff sebagai teks biasa.

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"

Jenis media Accept: application/vnd.github.diff mengubah respons dari objek JSON yang menjelaskan pull request menjadi unified diff itu sendiri, dan gh api mencetak isi tersebut tanpa perubahan. Baris pertama yang Anda lihat seharusnya diawali dengan diff --git a/. Kode gh: Not Found (HTTP 404) berarti token tidak dapat melihat repositori. Pada personal token dengan akses terperinci, hal ini hampir selalu berarti izin Pull requests tidak diaktifkan.

Filter sebelum Anda menghabiskan token

Bagian ini menentukan apakah orang akan membaca bot atau membisukannya. Setiap filter di bawah ini berjalan sebelum model melihat satu byte pun.

  • Filter path. Abaikan file lock, direktori vendored, bundle yang telah diminifikasi, dan kode yang dibuat secara otomatis. Komentar model tentang package-lock.json hanya menambah noise, dan file-file tersebut sering mencakup sebagian besar byte dalam diff.
  • Batas ukuran. Jika melebihi batas, lewati review dan keluar dengan status green. Refactor sepanjang 4,000 baris cukup mendapat satu keterangan yang menyatakan bahwa ukurannya terlalu besar untuk direview secara otomatis, bukan enam puluh tebakan.
  • Ambang tingkat keparahan dan batas jumlah komentar. Laporkan temuan tingkat tinggi dan sedang, hingga sepuluh temuan, dengan tingkat keparahan tertinggi terlebih dahulu. Tidak ada yang membaca komentar ke-11.

Skrip

Simpan ini sebagai /opt/pr-review/review.py. Skrip membaca konfigurasinya dari environment, sehingga workflow dapat mengganti model tanpa perubahan kode.

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

Pemisahan diff berdasarkan file memungkinkan filtering path. Penomoran setiap baris membuat komentar review ditempatkan pada lokasi yang tepat. GitHub hanya menerima inline comment pada baris yang merupakan bagian dari diff, sehingga model harus mencantumkan nomor baris yang benar. Dengan memberikan nomor tersebut, model dapat menyalin salah satunya dan tidak mengarang nomor.

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)

Header hunk memuat informasi penomoran. @@ -12,7 +12,9 @@ menyatakan bahwa hunk file baru dimulai pada baris 12, sehingga penghitung dimulai dari sana dan bertambah hanya pada baris yang ditambahkan atau tidak berubah. Baris yang dihapus dilewati tanpa nomor karena baris tersebut tidak ada dalam file baru. Guard pada baris yang diawali backslash melewati penanda tidak adanya newline yang ditulis git di akhir file. Tanpa guard ini, semua nomor berikutnya akan bergeser satu.

Kedua exit menggunakan status 0, bukan 1. Pull request yang difilter atau terlalu besar harus menampilkan pemeriksaan berstatus hijau. Pemeriksaan berstatus merah yang tidak dapat ditindaklanjuti manusia akan diabaikan. Setelah satu pemeriksaan diabaikan, semuanya akan diabaikan.

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

Ada tiga detail penting dalam blok tersebut. JSON dipotong di antara { pertama dan } terakhir karena model terkadang membungkus jawabannya dalam code fence, sedangkan json.loads akan gagal memproses code fence tersebut. path dihapus awalan b/-nya karena prefix tersebut berasal dari diff header, sedangkan GitHub mengharapkan path relatif terhadap repository. --input - mengirim seluruh review dalam satu panggilan API, sehingga sepuluh temuan tiba sebagai satu notifikasi, bukan sepuluh notifikasi.

Jika tidak ada yang perlu dilaporkan, skrip tidak mengirim apa pun. Bot yang menulis "tidak ada masalah yang ditemukan" pada setiap pull request membuat orang terbiasa melewatinya. Akibatnya, mereka juga dapat melewatkan satu laporan yang benar-benar penting.

Hubungkan ke alur kerja

Simpan ini sebagai .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 tidak berada dalam blok env: tersebut karena Actions sudah menetapkannya untuk setiap job. Grup concurrency penting untuk biaya: tanpa grup tersebut, jika Anda mendorong tiga perbaikan cepat ke sebuah branch, tiga review lengkap akan dijalankan dan Anda membayar ketiganya. Dengan grup tersebut, hanya perbaikan terakhir yang diproses.

Baris if: memiliki dua fungsi. Bagian pertama melewati pull request dari fork karena pull request tersebut akan gagal tanpa key. Bagian kedua menyediakan sakelar untuk tim Anda: tambahkan label no-ai-review ke pull request agar job tidak dijalankan.

Buka pull request dan pantau hasilnya:

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

Run yang selesai dalam beberapa detik dan menampilkan nothing above the severity threshold; posting no comment di log berarti konfigurasi bekerja dengan benar. Pada pull request kecil dan bersih, hasil tersebut memang diharapkan.

Berapa biaya tinjauan pull request otomatis?

Diff hampir seluruhnya merupakan input, sehingga ukuran diff menentukan harga. Berikut satu diff berukuran 500 baris yang diukur bersama system prompt. Penghitungan dilakukan dengan endpoint penghitung token, bukan berdasarkan estimasi.

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

Diff tersebut menghasilkan 8,000 token input pada Haiku 4.5 dan 10,400 pada Sonnet 5. Teksnya sama, tetapi jumlah tokennya berbeda. Model Claude mulai versi 4.7 menggunakan tokenizer yang lebih baru dan menghasilkan sekitar 30% lebih banyak token untuk input yang sama. Anthropic mendokumentasikan hal ini pada halaman harganya. Perhitungkan perbedaan tersebut saat membandingkan harga model baru dan model lama hanya berdasarkan harga per juta token.

Harga daftar per Agustus 2026: Haiku 4.5 berharga $1 per juta token input dan $5 per juta token output. Sonnet 5 berharga $2 dan $10 berdasarkan harga perkenalan yang berlaku sampai 31 August 2026, kemudian menjadi $3 dan $15. Opus 5 berharga $5 dan $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"
  }
]

Biayanya adalah 1.4 sen per pull request pada Haiku 4.5 dan 9.1 sen pada Opus 5. Tim yang menggabungkan 200 pull request per bulan membayar sekitar $2.80 pada Haiku 4.5, $7.28 pada Sonnet 5, atau $18.20 pada Opus 5. Mulai 1 September 2026, kalikan baris Sonnet 5 dengan 1.5.

Dua hal dapat membuat tagihan sebenarnya lebih tinggi daripada estimasi tersebut. Trigger synchronize meninjau setiap push, sehingga branch aktif dengan delapan push menimbulkan biaya delapan tinjauan. Aturan konkurensi hanya membantu jika beberapa push terjadi dalam waktu yang berdekatan. Angka tersebut juga mengasumsikan bahwa filter path berfungsi. Satu lock file yang tidak difilter dapat menggandakan input dengan sendirinya.

Prompt caching tidak membantu dalam kasus ini. Prefix yang di-cache harus identik byte demi byte di antara beberapa panggilan, sedangkan diff selalu berubah. System prompt adalah satu-satunya bagian yang stabil, tetapi ukurannya jauh di bawah panjang minimum yang dapat di-cache. Untuk aturan umum, lihat kapan prompt caching mulai menguntungkan, dan untuk memilih di antara tiga model tersebut, lihat model Claude yang digunakan untuk setiap pekerjaan.

Ukur diff Anda sendiri sebelum mengaktifkannya

Tambahkan baris ini setelah payload dibuat, lalu jalankan skrip secara manual terhadap beberapa pull request dari bulan lalu:

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

Endpoint penghitung tidak menjalankan model, sehingga tidak menggunakan token input atau output. Endpoint tersebut menggunakan tokenizer milik model yang Anda tentukan. Jalankan endpoint tersebut pada sepuluh pull request nyata dari repositori Anda sendiri dan gunakan nilai median, bukan nilai rata-rata, agar satu migrasi besar tidak mengubah estimasi.

Mengapa bot review dibisukan, dan cara mencegahnya

Dua perilaku merusak kepercayaan terhadap bot ini. Keduanya dapat diperbaiki dalam kode di atas.

Meninjau semuanya sekaligus. Bot yang meninggalkan empat puluh komentar tidak akan dibaca satu pun komentarnya. Ambang batas tingkat keparahan dan batas sepuluh komentar bukan sekadar bentuk kesopanan. Keduanya menjaga agar temuan yang benar-benar penting tetap terlihat. Mengurutkan berdasarkan tingkat keparahan sebelum memotong daftar memastikan batas tersebut menghapus temuan yang paling tidak penting, bukan sepuluh temuan secara acak.

Memberikan komentar dengan yakin tentang sesuatu yang tidak dapat diperiksanya. Inilah yang membuat engineer menonaktifkan bot untuk selamanya. Model yang hanya diberi 200 baris dari codebase berisi 40,000 baris tetap dapat menulis, "ini merusak invalidasi cache pada redis_client.py", tentang file yang belum pernah dilihatnya. System prompt menegaskan hal ini dengan bahasa yang jelas: laporkan hanya defect yang terlihat pada baris yang ditampilkan, dan abaikan hal yang tidak dapat dipastikan. Menyebutkan kegagalan secara langsung lebih efektif daripada meminta akurasi secara umum. Memberi tahu model bahwa hasil kosong adalah hal yang normal juga mencegahnya mengarang sesuatu untuk dikatakan tentang perubahan dua baris.

Publikasikan review sebagai COMMENT, jangan sebagai REQUEST_CHANGES. Pendapat model tidak boleh dapat memblokir merge. Begitu pendapat tersebut dapat memblokir merge, seseorang yang sedang dikejar tenggat akan menghapus seluruh workflow daripada berdebat dengan bot.

Mode kegagalan dan string yang akan Anda lihat

HTTP 422 saat mengirim review. gh mencetak gh: Unprocessable Entity (HTTP 422), dan isi respons menyebutkan field tersebut: Pull request review thread line must be part of the diff. GitHub tidak dapat menempatkan komentar itu pada lokasi yang sesuai. Penyebab yang umum adalah nomor baris yang dibuat-buat oleh model, path yang masih membawa awalan b/, atau komentar pada baris yang dihapus, yang memerlukan side diatur ke LEFT, bukan RIGHT. Cetak JSON review sebelum mengirimkannya, lalu periksa satu komentar secara manual terhadap diff.

invalid x-api-key dari model API. Langkah gagal pada pemanggilan messages.create pertama. Kemungkinan ANTHROPIC_API_KEY secret belum diatur pada repository, atau pull request berasal dari fork sehingga Actions sama sekali tidak meneruskan secret. Guard fork pada baris if: seharusnya melewati langkah ini, jadi periksa baris tersebut terlebih dahulu.

gh: Resource not accessible by integration (HTTP 403). Token job tidak dapat menulis ke pull request. Tambahkan pull-requests: write ke blok permissions:. Jika sudah ada, buka Settings, lalu Actions, kemudian General. Kebijakan organisasi dapat membatasi izin yang boleh diminta oleh token workflow.

json.decoder.JSONDecodeError. Model tidak mengembalikan JSON yang dapat di-parse. Penyebab yang umum adalah respons mencapai batas token dan berhenti di tengah objek. Baris log mencetak stop_reason khusus untuk kondisi ini: nilai max_tokens berarti Anda harus menaikkan max_tokens atau menurunkan MAX_COMMENTS.

Workflow tidak pernah berjalan. gh run list tidak menampilkan apa pun untuk pull request tersebut. Periksa apakah paths-ignore tidak memfilter semua file yang berubah. Setelah itu, periksa fork guard dan label guard, lalu pastikan runner aktif dengan sudo systemctl status 'actions.runner.*' pada VPS. Runner yang offline membuat job tetap berada dalam antrean tanpa pesan error apa pun pada pull request.

Setiap review kembali tanpa temuan. Atur MIN_SEVERITY ke low untuk satu kali eksekusi. Jika temuan muncul, berarti threshold berfungsi. Jika tidak ada temuan, cetak payload dan pastikan filter tidak menghapus seluruh diff.

Menjalankannya bersama agent lain

Reviewer ini berukuran kecil, sehingga Anda mungkin tergoda memasangnya pada server yang juga menjalankan semua layanan lain. Tetap pisahkan jika repository tersebut penting. Proses ini menyimpan token yang dapat menambahkan komentar pada kode Anda dan key yang dapat membelanjakan uang Anda. Selain itu, self-hosted runner memang dirancang sebagai tempat untuk mengeksekusi kode workflow. Akun khusus tanpa hak sudo pada host yang tidak menjalankan layanan lain adalah baseline. Jika Anda juga menjalankan agent interaktif yang melakukan checkout kode, satu VM sekali pakai untuk setiap agent adalah pola yang lebih andal. Menjalankan coding agent pada VPS membahas penyiapan umum. Jika Anthropic API masih baru bagi Anda, aplikasi Claude API pertama pada VPS merupakan titik awal yang lebih sederhana daripada penyiapan ini.

FAQ

Apakah agen peninjau PR berbasis AI memerlukan akses tulis ke repositori saya?

Tidak. Agen tersebut memerlukan pull-requests: write untuk memublikasikan hasil peninjauan dan contents: read untuk mengambil diff. Hanya itu yang diperlukan, dan Anda menetapkannya dalam blok permissions: pada workflow. Blok ini membatasi kemampuan GITHUB_TOKEN untuk setiap job. Dengan dua baris tersebut, agen dapat memberikan komentar pada pull request, tetapi tidak dapat melakukan push commit atau melakukan merge branch. Gunakan event: COMMENT untuk memublikasikan hasil peninjauan, bukan REQUEST_CHANGES, agar agen juga tidak dapat memblokir merge.

Mengapa komentar peninjauan saya gagal dengan HTTP 422?

GitHub hanya menerima komentar peninjauan inline pada baris yang merupakan bagian dari diff pull request. GitHub mengembalikan Pull request review thread line must be part of the diff jika baris tersebut bukan bagian dari diff. Pastikan path bersifat relatif terhadap repositori dan tidak memiliki prefiks b/ dari header diff. Pastikan juga nomor baris tersebut berada di dalam hunk file itu. side harus berupa RIGHT untuk baris yang ditambahkan atau tidak berubah, dan LEFT untuk baris yang dihapus. Berikan nomor baris file baru di awal setiap baris diff sebelum mengirimkannya ke model. Cara ini mencegah model mengarang nomor sejak awal.

Apakah saya dapat menjalankan ini pada repositori publik dengan pull request dari fork?

Tidak dengan desain ini. GitHub tidak meneruskan secret ke workflow yang dipicu dari fork. Akibatnya, model key tidak tersedia dan eksekusi gagal. GitHub juga menyatakan bahwa self-hosted runner "should almost never be used for public repositories" karena siapa pun dapat membuka pull request yang menyebabkan kode dijalankan pada mesin Anda. Untuk project publik, batasi reviewer pada branch yang di-push ke repositori itu sendiri, seperti yang dilakukan oleh guard if:, atau pindahkan tahap peninjauan ke runner yang di-host GitHub dan terima bahwa diff akan keluar dari infrastruktur Anda sendiri.

Model mana yang harus saya gunakan untuk peninjauan pull request?

Mulailah dengan Haiku 4.5. Membaca diff yang dibatasi berdasarkan daftar tetap jenis defect bukanlah masalah penalaran yang sulit. Model dengan biaya terendah juga menjaga tagihan bulanan tetap pada tingkat yang mudah diterima semua pihak. Gunakan Sonnet 5 jika Anda menemukan bahwa model tersebut melewatkan bug nyata dalam bahasa pemrograman atau framework Anda. Ukur hasilnya, jangan hanya berasumsi. Opus 5 adalah yang paling mahal dari ketiganya dengan selisih besar untuk setiap pull request. Model ini lebih mudah dibenarkan untuk branch rilis daripada untuk setiap push ke setiap branch fitur.