SSD Nodes Learn 🎉 VPS $4.99/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-07

Kendi VPS Sunucunuzda AI PR İnceleme Aracı Kurulumu

Kendi VPS sunucunuz üzerinde çalışan bir AI PR inceleme aracını nasıl kuracağınızı öğrenin. Diff kapsamlı istemler, dosya filtreleri ve maliyet yönetimi ile güvenli kod analizi yapın.

Self-hosted bir PR inceleme aracının işlevi

Self-hosted bir PR inceleme aracı, kendi sunucunuzda çalışan küçük bir programdır. Bir pull request (PR) içindeki diff verisini okur ve yalnızca değiştirilen satırları bir modele gönderir. Gelen yanıt, satır içi inceleme yorumu olarak paylaşılır. Bu araç hiçbir zaman branch'inizi checkout etmez ve pull request ile değiştirilmeyen hiçbir dosyayı okumaz. Sahip olduğu tek kimlik bilgisi, bir model API (application programming interface) anahtarı ve yalnızca yorum yapma yetkisine sahip, başka hiçbir işlem yapamayan bir tokendır.

Bir model, diff verisini okuyabilir. Bu kısım çözülmüş bir sorundur. Önemli olan, diff verisinin nereye gittiği ve anahtarı kimin tuttuğudur. Hosted bir inceleme botu kullanmak, tüm özel depolarınızdaki diff verilerinin ağınızdan çıkması, üçüncü tarafın loglarına düşmesi ve onların veri saklama politikalarına tabi olması anlamına gelir. Kendi VPS (virtual private server) sunucunuzda ise diff verisi GitHub'dan sizin sunucunuza, oradan da model API'sine gider; ayrıca neyin gönderileceğine karar veren kırk satırlık kodu kendiniz inceleyebilirsiniz.

Başlamadan önce gerekenler

  • GitHub Actions runner'ı halihazırda depoya kayıtlı olan, Ubuntu 24.04 çalıştıran bir VPS. Kayıt işlemi sırasında kendi kendine barındırılan GitHub Actions runner'a pr-review etiketini ekleyin; aşağıdaki iş akışı bu etiketi temel alarak seçim yapacaktır.
  • Claude Console üzerinden alınmış bir Anthropic API anahtarı.
  • Pull request açma yetkisini kontrol edebildiğiniz bir depo. Özel (private) depolar en kolay senaryodur. Aşağıdaki fork bölümü herkese açık (public) depoları kapsar, ancak oradaki çözüm daha zahmetlidir.

Reviewer yazılımının VPS üzerine kurulması

Runner servisi, ./svc.sh install komutunu çalıştırdığınızda oluşturduğunuz yetkisiz hesap altında çalışır. İşlerin sudo yetkisi gerektirmeden yürütülebilmesi için reviewer yazılımını aynı hesap altında kurun. Aşağıdaki runner ifadesini kendi hesap adınızla değiştirin.

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 komutu, Ağustos 2026 itibarıyla Ubuntu 24.04 üzerinde gh version 2.45.0 çıktısını verir. 2.20 ve sonraki tüm sürümler, aşağıda kullanılan --input bayrağını destekler. Bir Command 'gh' not found mesajı, universe bileşeninin etkinleştirilmediği anlamına gelir; bu durumda sudo add-apt-repository universe komutunu çalıştırın ve tekrar deneyin.

Anahtar ve belirtecin konumu

İki gizli veri, iki farklı yaşam süresi. İkisi de depoya (repository) eklenmemelidir.

ANTHROPIC_API_KEY, Settings, ardından Secrets and variables ve sonrasında Actions altında ayarlanan bir depo gizli verisidir. GitHub bunu şifreler ve çalışma zamanında adımın ortamına enjekte eder. Bu veri hiçbir zaman diskte bir dosya olarak bulunmaz ve git geçmişine girmez.

GITHUB_TOKEN farklı çalışır. Actions, her iş için yeni bir belirteç oluşturur ve iş bittiğinde bu belirteci yok eder. Bu belirtecin neler yapabileceği, iş akışındaki permissions: bloğu ile belirlenir; dolayısıyla en az yetki prensibi tam olarak burada uygulanır:

permissions:
  contents: read
  pull-requests: write

Bu belirteç bir inceleme (review) gönderebilir. Commit gönderemez, dal birleştiremez, iş akışı dosyasını düzenleyemez veya başka bir depoya dokunamaz. Yorum yapabilen bir aracı, bir inceleyicidir. Commit gönderebilen bir aracı ise bir committer'dır ve kimse buna onay vermemiştir. Model anahtarına da aynı özeni gösterin, çünkü bu anahtar hesabınız üzerinden harcama yapar. Bu tür sorunların yapısı hakkında daha fazla bilgi için yapay zeka aracısının erişiminden gizli verileri koruma bölümüne bakabilirsiniz.

Actions, iş günlüklerinde tam gizli veri dizisini *** ile değiştirir. Bu işlem yalnızca tam eşleşen dizeler için geçerlidir; bu nedenle base64 ile kodladığınız, iki satıra böldüğünüz veya karakter karakter yazdırdığınız bir anahtar açık metin olarak görünür. Ortamı döken bir hata ayıklama (debug) adımı eklemeyin.

Fork üzerinden gelen bir pull request neden API anahtarınızı görmez

GitHub kuralı kısadır: GITHUB_TOKEN istisnası haricinde, bir iş akışı (workflow) fork edilmiş bir depodan tetiklendiğinde gizli veriler (secrets) çalıştırıcıya (runner) aktarılmaz. Bu nedenle, fork üzerinden başlatılan bir pull_request, betiğinizi herhangi bir ANTHROPIC_API_KEY olmadan başlatır ve ilk API çağrısı invalid x-api-key hatasıyla başarısız olur.

Akla gelen ilk çözüm, tetikleyiciyi pull_request_target olarak değiştirmektir; bu tetikleyici ana deponun bağlamında çalışır ve gizli verilere erişebilir. Bunu burada yapmayın. GitHub'ın kendi güvenlik kılavuzu, bu tür iş akışlarının "ayrıcalıklı olduğunu, yani ana dalın önbelleğini diğer ayrıcalıklı iş akışı tetikleyicileriyle paylaştığını, depo yazma erişimine ve referans verilen gizli verilere erişim yetkisine sahip olabileceğini" belirtir ve sonucun "depoyu ele geçirmek için istismar edilebileceği" konusunda uyarır.

Aynı kılavuz, çalıştırıcılar konusunda da nettir: "Self-hosted çalıştırıcılar, GitHub üzerindeki herkese açık depolar için neredeyse hiçbir zaman kullanılmamalıdır; çünkü herhangi bir kullanıcı depoya pull request gönderebilir ve ortamı tehlikeye atabilir."

Bu durum iki tasarım tercihini zorunlu kılar. İş, yalnızca kendi deponuza gönderilen dallarda çalışacak bir korumaya sahiptir. Ayrıca iş akışında hiçbir actions/checkout adımı bulunmaz. Aracı, dalı hiçbir zaman disk üzerinde tutmaz; bu nedenle kötü niyetli bir pull request, yalnızca bir modele gönderilen metinden ibarettir. VPS'niz üzerinde bir derleme betiği çalıştıramaz, çünkü VPS'niz üzerinde hiçbir şey bunu çalıştırmaz.

Depoyu değil, farkı (diff) çekin

Tek bir istek, tüm farkı düz metin olarak getirir.

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"

Accept: application/vnd.github.diff medya türü, yanıtı pull request'i tanımlayan bir JSON nesnesinden birleşik farkın (unified diff) kendisine dönüştürür ve gh api bu gövdeyi değiştirilmeden yazdırır. Gördüğünüz ilk satır diff --git a/ ile başlamalıdır. Bir gh: Not Found (HTTP 404) hatası, token'ın depoyu göremediği anlamına gelir; bu durum, kapsamlı (fine-grained) kişisel token'larda neredeyse her zaman Pull requests izninin verilmediği anlamına gelir.

Token harcamadan önce filtreleme yapın

Bu bölüm, insanların okuduğu bir bot ile insanların sessize aldığı bir bot arasındaki farkı belirler. Aşağıdaki her bir filtre, model tek bir bayt bile görmeden önce çalışır.

  • Yol filtreleri. Kilit dosyaları, vendored dizinleri, küçültülmüş (minified) paketler ve oluşturulmuş kodlar. package-lock.json üzerindeki bir model yorumu tamamen gürültüdür ve bu dosyalar genellikle bir diff içindeki baytların çoğunluğunu oluşturur.
  • Boyut sınırı. Sınır aşıldığında incelemeyi atlayın ve başarılı (green) olarak çıkış yapın. 4.000 satırlık bir refactor işlemi için altmış tane tahminde bulunmak yerine, otomatik inceleme için çok büyük olduğuna dair tek bir dürüst satır döndürün.
  • Önem derecesi eşiği ve yorum sınırı. Yüksek ve orta seviyeli bulguları, en yüksek önem derecesinden başlayarak en fazla on taneye kadar raporlayın. On birinci yorumu kimse okumaz.

Betik

Bunu /opt/pr-review/review.py olarak kaydedin. Yapılandırmasını ortam değişkenlerinden okur; böylece iş akışı, kod değişikliği gerektirmeden modelleri değiştirebilir.

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

Farkın dosya bazında ayrıştırılması, yol filtrelemeyi mümkün kılar. Her satırın numaralandırılması ise inceleme yorumlarının doğru yere ulaşmasını sağlar. GitHub, satır içi yorumları yalnızca diff'in bir parçası olan satırlara kabul eder; bu nedenle modelin gerçek bir satır numarası belirtmesi gerekir. Numaraların kendisine verilmesi, modelin uydurmak yerine mevcut olanı kopyalamasını sağlar.

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)

Hunk başlığı numaralandırmayı taşır. @@ -12,7 +12,9 @@, yeni dosyanın hunk kısmının 12. satırdan başladığını belirtir; bu yüzden sayaç oradan başlar ve yalnızca eklenen veya değişmeyen satırlarda ilerler. Silinen satırlar numaralandırılmaz, çünkü yeni dosyada mevcut değildirler. Ters eğik çizgi ile başlayan satırlardaki koruma, git'in dosya sonunda yazdığı ve aksi takdirde sonraki tüm numaraları bir kaydıracak olan "no-newline" işaretçisini atlar.

Her iki çıkış da 1 yerine 0 durum kodunu kullanır. Filtrelenmiş veya boyut sınırını aşan bir pull request yeşil onay işareti göstermelidir. İnsanın müdahale edemeyeceği kırmızı bir onay işareti görmezden gelinir; bir kez görmezden gelindiğinde ise hepsi görmezden gelinmeye başlanır.

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

Bu bloktaki üç detay kritik öneme sahiptir. JSON, ilk { ile son } arasından dilimlenir; çünkü model bazen cevabını bir kod bloğu içine alır ve json.loads bu blokla çalışamaz. path, başındaki b/ ifadesinden arındırılır; çünkü bu önek diff başlığından gelir ve GitHub depo ile ilişkili bir yol bekler. Ayrıca --input -, tüm incelemeyi tek bir API çağrısı olarak gönderir; böylece on bulgu, on ayrı bildirim yerine tek bir bildirim olarak ulaşır.

Raporlanacak bir şey olmadığında betik hiçbir şey göndermez. Her pull request altına "sorun bulunamadı" yazan bir bot, insanların onu göz ardı etmesine neden olur; bu durumda gerçekten önemli olan bildirim de gözden kaçırılır.

İş akışına entegre edin

Bunu .github/workflows/pr-review.yml olarak kaydedin:

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, env: bloğunda yer almaz çünkü Actions bunu her iş için zaten otomatik olarak ayarlar. concurrency grubu faturalandırma açısından önemlidir: bu grup olmadan, bir dala yapılan üç hızlı düzeltme üç tam incelemeyi tetikler ve üçü için de ödeme yaparsınız; bu grup ile ise yalnızca sonuncusu geçerli kalır.

if: satırı iki işlevi yerine getirir. İlk kısım, zaten anahtar eksikliği nedeniyle başarısız olacak olan fork'lardan gelen pull request'leri atlar. İkinci kısım ise ekibinize bir kapatma anahtarı sunar: bir pull request'e no-ai-review etiketini eklerseniz iş çalışmaz.

Bir pull request açın ve süreci izleyin:

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

Log dosyasında nothing above the severity threshold; posting no comment ile birkaç saniye içinde tamamlanan bir çalışma, doğru şekilde işliyor demektir. Küçük ve temiz bir pull request üzerinde beklenen sonuç budur.

Otomatik bir pull request incelemesinin maliyeti nedir?

Diff, girdinin neredeyse tamamını oluşturduğundan, fiyatı diff boyutu belirler. Aşağıda, tahmin yerine token sayma uç noktası ile hesaplanmış, 500 satırlık bir diff ve sistem istemi örneği yer almaktadır.

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

Bu diff, Haiku 4.5 üzerinde 8,000 girdi token'ına, Sonnet 5 üzerinde ise 10,400 girdi token'ına karşılık gelmektedir. Metin aynı olsa da sayı farklıdır. 4.7 sürümünden itibaren Claude modelleri, Anthropic'in fiyatlandırma sayfasında belirttiği üzere, aynı girdi için yaklaşık %30 daha fazla token üreten yeni bir tokenizer kullanmaktadır. Daha yeni bir modeli eskisiyle yalnızca milyon token başına fiyat üzerinden kıyaslarken bunu dikkate alın.

Ağustos 2026 itibarıyla liste fiyatları şöyledir: Haiku 4.5, milyon girdi token'ı başına 1 dolar ve milyon çıktı token'ı başına 5 dolardır. Sonnet 5, 31 Ağustos 2026'ya kadar sürecek tanıtım fiyatlandırması kapsamında 2 dolar ve 10 dolar, sonrasında ise 3 dolar ve 15 dolardır. Opus 5 ise 5 dolar ve 25 dolardır.

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

Bu, Haiku 4.5 üzerinde pull request başına 1.4 sent, Opus 5 üzerinde ise 9.1 sent maliyet anlamına gelir. Ayda 200 pull request birleştiren bir ekip, Haiku 4.5 üzerinde yaklaşık $2.80, Sonnet 5 üzerinde $7.28 veya Opus 5 üzerinde $18.20 öder. 1 Eylül 2026'dan itibaren Sonnet 5 satırını 1.5 ile çarpın.

İki durum, gerçek faturayı bu tahminin üzerine çıkarır. synchronize her push işleminde incelemeyi tetikler; bu nedenle sekiz push içeren aktif bir dal sekiz inceleme maliyeti doğurur ve eşzamanlılık kuralı yalnızca push işlemleri birbirine yakın gerçekleştiğinde yardımcı olur. Rakamlar ayrıca yol filtrelerinin çalıştığını varsayar: filtrelenmemiş tek bir kilit dosyası (lock file), girdi boyutunu tek başına iki katına çıkarabilir.

Prompt caching burada işe yaramaz. Önbelleğe alınan önekin çağrılar arasında bayt düzeyinde aynı olması gerekir, ancak diff her seferinde farklıdır. Sistem istemi tek sabit kısımdır ve minimum önbelleklenebilir uzunluğun çok altında kalır. Genel kural için prompt caching ne zaman kendini amorti eder kısmına, yukarıdaki üç model arasında seçim yapmak için ise hangi iş için hangi Claude modeli kullanılmalı kısmına bakın.

Özelliği açmadan önce kendi diff'lerinizi ölçün

payload oluşturulduktan sonra bu satırı ekleyin, ardından betiği geçen ayın birkaç pull request'i üzerinde manuel olarak çalıştırın:

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

Sayma uç noktası modeli çalıştırmaz, bu nedenle girdi veya çıktı token'ı tüketmez ve belirttiğiniz modele ait tokenizer'ı kullanır. Kendi deponuzdan on gerçek pull request üzerinde çalıştırın ve ortalamayı değil medyanı alın; böylece devasa bir taşıma işlemi (migration) tahmini saptırmaz.

İnceleme botlarının neden sessize alındığı ve bunun nasıl önleneceği

İki davranış bu botlara duyulan güveni yok eder ve her ikisinin de yukarıdaki kodda bir çözümü mevcuttur.

Her şeyi aynı anda incelemek. Kırk yorum bırakan bir bot, bu yorumların hiçbirinin okunmamasına neden olur. Önem derecesi eşiği ve on yorum sınırı nezaket gereği değil, gerçek bulguların görünür kalmasını sağlayan unsurlardır. Kısaltma işleminden önce önem derecesine göre sıralama yapmak, sınırın rastgele on bulgu yerine en az önemli olanları elemesini sağlar.

Kontrol edemediği bir konuda emin bir şekilde yorum yapmak. Mühendislerin botu tamamen kapatmasına neden olan durum budur. 40.000 satırlık bir kod tabanının 200 satırını gören bir model, daha önce hiç görmediği bir dosya hakkında "bu, redis_client.py içindeki önbellek geçersiz kılma işlemini bozuyor" şeklinde yorum yapabilir. Sistem istemi, bunu açık bir dille engeller: yalnızca gösterilen satırlarda görülebilen kusurları raporlayın ve emin olmadığınız her şeyi dışarıda bırakın. Hatayı doğrudan isimlendirmek, genel olarak doğruluk istemekten daha iyi sonuç verir; modele boş bir sonucun normal olduğunu söylemek, iki satırlık bir değişiklik hakkında söyleyecek bir şey uydurmasını engeller.

İncelemeyi COMMENT olarak gönderin, asla REQUEST_CHANGES olarak göndermeyin. Bir modelin görüşü bir birleştirmeyi (merge) engelleyememelidir; engelleyebildiği anda, teslim tarihi olan biri, botla tartışmak yerine tüm iş akışını kaldıracaktır.

Hata modları ve karşılaşacağınız dizgeler

İnceleme gönderilirken HTTP 422 hatası. gh, gh: Unprocessable Entity (HTTP 422) çıktısını verir ve yanıt gövdesinde ilgili alan belirtilir: Pull request review thread line must be part of the diff. GitHub bu yorumu sabitleyemez. Yaygın nedenler; modelin uydurduğu bir satır numarası, hala b/ öneki taşıyan bir path veya kaldırılmış bir satıra yapılan yorumdur; bu durumda side değerinin RIGHT yerine LEFT olarak ayarlanması gerekir. Göndermeden önce inceleme JSON verisini yazdırın ve bir yorumu diff ile manuel olarak karşılaştırın.

Model API'sinden invalid x-api-key hatası. Adım, ilk messages.create çağrısında başarısız olur. Ya ANTHROPIC_API_KEY gizli anahtarı depoda ayarlanmamıştır ya da pull request bir fork üzerinden geldiği için Actions hiçbir gizli anahtarı aktarmamıştır. if: satırındaki fork koruması bunu atlamalıydı, bu yüzden önce o satırı kontrol edin.

gh: Resource not accessible by integration (HTTP 403). İş belirteci (job token) pull request'lere yazamaz. permissions: bloğuna pull-requests: write ekleyin. Eğer zaten ekliyse, Settings, ardından Actions ve General kısmına bakın; burada bir organizasyon politikası, herhangi bir iş akışı belirtecinin talep edebileceklerini kısıtlıyor olabilir.

json.decoder.JSONDecodeError. Model, ayrıştırılabilir bir JSON döndürmedi. Yaygın neden, yanıtın token sınırına ulaşıp nesnenin ortasında kesilmesidir. Günlük satırı tam olarak bunun için stop_reason yazdırır: max_tokens değeri, max_tokens değerini yükseltmeniz veya MAX_COMMENTS değerini düşürmeniz gerektiği anlamına gelir.

İş akışı hiç çalışmıyor. gh run list, pull request için hiçbir şey göstermiyor. paths-ignore değerinin değişen tüm dosyaları filtrelemediğinden emin olun, ardından fork korumasını ve etiket korumasını kontrol edin; son olarak VPS üzerinde sudo systemctl status 'actions.runner.*' ile runner'ın çalışır durumda olup olmadığını denetleyin. Çevrimdışı bir runner, işi kuyrukta bekletir ve pull request içinde herhangi bir hata mesajı göstermez.

Her inceleme boş dönüyor. Bir çalıştırma için MIN_SEVERITY değerini low olarak ayarlayın. Eğer bulgular görünüyorsa, eşik değeri görevini yapıyordur. Eğer hiçbir şey görünmüyorsa, payload çıktısını alın ve filtrelerin tüm diff'i kaldırmadığını doğrulayın.

Diğer ajanlarınızla birlikte çalıştırma

Reviewer küçük bir araç olduğundan, onu halihazırda her şeyi çalıştıran sunucuya kurmak cazip gelebilir. Eğer depo güvenliği önemliyse, bu servisi ayrı tutun. Bu süreç, kodunuza yorum yapabilen bir token ve harcama yapabilen bir anahtar barındırır; ayrıca self-hosted runner, tasarım gereği iş akışı kodlarının yürütüldüğü bir ortamdır. Sudo yetkisi olmayan, özel bir düşük ayrıcalıklı hesap ve başka hiçbir şeyin çalışmadığı bir ana makine, temel güvenlik standardıdır. Eğer kod indiren etkileşimli ajanlar da çalıştırıyorsanız, her ajan için tek kullanımlık bir VM kullanmak geçerli bir yöntemdir ve bir VPS üzerinde kodlama ajanı çalıştırmak genel kurulumu kapsar. Anthropic API sizin için yeniyse, bir VPS üzerinde ilk Claude API uygulaması bu işe başlamak için daha küçük bir adımdır.

FAQ

Bir yapay zeka PR inceleme aracının depoma yazma erişimine ihtiyacı var mı?

Hayır. İnceleme göndermek için pull-requests: write ve diff verisini çekmek için contents: read yetkisine ihtiyaç duyar. Liste bundan ibarettir ve bu yetkileri, iş bazlı GITHUB_TOKEN yetkisini sınırlayan iş akışının permissions: bloğunda tanımlarsınız. Bu iki satır ile araç, bir pull request üzerine yorum yapabilir ancak commit gönderemez veya branch birleştiremez. İncelemeleri REQUEST_CHANGES yerine event: COMMENT ile gönderin; böylece birleştirme işlemini engellemesi de önlenmiş olur.

İnceleme yorumum neden HTTP 422 hatası veriyor?

GitHub, satır içi inceleme yorumlarını yalnızca pull request diff'inin bir parçası olan satırlarda kabul eder; aksi durumda Pull request review thread line must be part of the diff hatası döner. path değerinin diff başlığındaki b/ öneki olmadan, depo dizinine göreli (relative) olduğundan ve satır numarasının ilgili dosya bloğu (hunk) içinde yer aldığından emin olun. side, eklenen veya değişmeyen satırlar için RIGHT, silinen satırlar için ise LEFT olmalıdır. Diff'in her satırını modele göndermeden önce yeni dosya satır numarasıyla öneklemek, modelin kendi kendine numara uydurmasını engeller.

Bunu, fork'lardan gelen pull request'lerin olduğu herkese açık bir depoda çalıştırabilir miyim?

Bu tasarımla hayır. GitHub, fork'tan tetiklenen bir iş akışına secret bilgilerini aktarmaz; bu nedenle model anahtarı eksik kalır ve çalışma başarısız olur. GitHub ayrıca, herkesin makinenizde kod çalıştırmasına neden olabilecek bir pull request açabileceği için self-hosted runner'ların "herkese açık depolarda neredeyse hiçbir zaman kullanılmaması gerektiğini" belirtir. Herkese açık bir proje için inceleme aracını yalnızca doğrudan depoya gönderilen branch'lerle kısıtlayın (bunu if: koruması yapar) veya inceleme adımını GitHub tarafından barındırılan bir runner'a taşıyarak diff verisinin altyapınızdan çıkmasını kabul edin.

Pull request incelemesi için hangi modeli kullanmalıyım?

Haiku 4.5 ile başlayın. Sınırlı bir diff'i sabit bir hata türü listesine göre okumak zor bir mantıksal akıl yürütme problemi değildir; en ucuz model, aylık faturayı kimsenin itiraz etmeyeceği bir seviyede tutar. Eğer modelin kullandığınız dilde veya framework'te gerçek hataları gözden kaçırdığını fark ederseniz Sonnet 5 modeline geçin; bunu varsayımlarla değil, ölçüm yaparak belirleyin. Opus 5, pull request başına üç model arasında açık ara en pahalı olanıdır; bu maliyeti her feature branch'ine yapılan her push işleminde değil, release branch'lerinde gerekçelendirmek daha kolaydır.