VPS पर अपना AI PR Review Agent कैसे सेटअप करें
अपने VPS पर AI PR reviewer सेटअप करने का तरीका जानें। इसमें diff-scoped prompts, path filters, inline comments और प्रति PR आने वाली सटीक लागत की पूरी जानकारी दी गई है।
self-hosted PR review agent क्या करता है
self-hosted PR review agent आपके स्वयं के सर्वर पर चलने वाला एक छोटा प्रोग्राम है। यह pull request (PR) के diff को पढ़ता है और केवल बदली हुई लाइनों को एक model के पास भेजता है। जो प्रतिक्रिया प्राप्त होती है, उसे inline review comments के रूप में पोस्ट किया जाता है। यह कभी भी आपकी branch को checkout नहीं करता है और न ही किसी ऐसी फाइल को पढ़ता है जिसे pull request में नहीं बदला गया है। इसके पास केवल एक model API (application programming interface) key और एक ऐसा token होता है जो केवल comment कर सकता है, इसके अलावा और कुछ नहीं।
एक model diff को पढ़ सकता है। वह समस्या हल हो चुकी है। महत्वपूर्ण यह है कि diff कहाँ जाता है और key किसके पास है। hosted review bot का मतलब है कि हर private repository का हर diff आपके नेटवर्क से बाहर जाता है, किसी तीसरे पक्ष के logs में जमा होता है, और उनकी retention policy के अधीन रहता है। आपके अपने VPS (virtual private server) पर, diff GitHub से आपके सर्वर पर और फिर model API तक जाता है, और आप उन चालीस लाइनों के कोड को पढ़ सकते हैं जो यह तय करते हैं कि क्या भेजा जाना है।
शुरू करने से पहले आपको क्या चाहिए
- Ubuntu 24.04 पर चल रहा एक VPS, जिसमें a self-hosted GitHub Actions runner पहले से ही रिपॉजिटरी के साथ पंजीकृत हो। पंजीकरण के समय इसे
pr-reviewअतिरिक्त लेबल दें, क्योंकि नीचे दिया गया वर्कफ़्लो उसी लेबल का चयन करता है। - Claude Console से प्राप्त एक Anthropic API key।
- एक ऐसी रिपॉजिटरी जहाँ आप नियंत्रित कर सकें कि कौन pull request खोल सकता है। एक प्राइवेट रिपॉजिटरी सबसे सरल विकल्प है। नीचे दिया गया फोर्क (fork) अनुभाग पब्लिक रिपॉजिटरी के मामले को कवर करता है, और वहां इसका समाधान थोड़ा जटिल है।
VPS पर reviewer इंस्टॉल करें
Runner service उसी unprivileged account के रूप में चलती है जिसे आपने ./svc.sh install चलाते समय बनाया था। Reviewer को उसी account के अंतर्गत इंस्टॉल करें ताकि job बिना sudo के इसे execute कर सके। नीचे दिए गए runner को अपने 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 --versiongh --version अगस्त 2026 तक Ubuntu 24.04 पर gh version 2.45.0 प्रिंट करता है। 2.20 के बाद का कोई भी release नीचे उपयोग किए गए --input flag का समर्थन करता है। Command 'gh' not found संदेश का अर्थ है कि universe component सक्षम नहीं है, इसलिए sudo add-apt-repository universe चलाएं और पुनः प्रयास करें।
key और token कहाँ रहते हैं
दो secrets, दो अलग-अलग lifetimes। इनमें से कोई भी repository में नहीं जाता है।
ANTHROPIC_API_KEY एक repository secret है, जिसे Settings, फिर Secrets and variables, और उसके बाद Actions के अंतर्गत सेट किया जाता है। GitHub इसे encrypt करता है और run time पर step के environment में inject करता है। यह कभी भी disk पर कोई file नहीं होती और न ही git history में रहती है।
GITHUB_TOKEN अलग तरह से काम करता है। Actions हर job के लिए एक नया token बनाता है और job खत्म होने पर उसे नष्ट कर देता है। वह token क्या कर सकता है, यह workflow में permissions: block द्वारा निर्धारित होता है, इसलिए यहीं पर वास्तव में least privilege लागू होता है:
permissions:
contents: read
pull-requests: writeवह token एक review post कर सकता है। वह commit push नहीं कर सकता, branch merge नहीं कर सकता, workflow file edit नहीं कर सकता, या किसी अन्य repository को छू नहीं सकता। जो agent comment कर सकता है, वह एक reviewer है। जो agent push कर सकता है, वह एक committer है, और इसके लिए किसी ने सहमति नहीं दी थी। model key के साथ भी उतनी ही सावधानी बरतें, क्योंकि यह आपके account पर पैसे खर्च करती है। इस तरह की समस्या के बारे में अधिक जानकारी AI agent की पहुँच से secrets को दूर रखना में दी गई है।
Actions job logs में सटीक secret string को *** से बदल देता है। यह केवल सटीक string से मेल खाता है, इसलिए यदि आप किसी key को base64 encode करते हैं, दो lines में विभाजित करते हैं, या एक-एक character करके print करते हैं, तो वह clear text में दिखाई देगा। ऐसा कोई debug step न जोड़ें जो environment को dump करता हो।
Pull request से API key क्यों नहीं दिखती
GitHub का नियम स्पष्ट है: GITHUB_TOKEN के अपवाद को छोड़कर, जब कोई workflow forked repository से trigger होता है, तो secrets को runner तक नहीं भेजा जाता है। इसलिए, fork से चलाया गया pull_request आपके script को बिना ANTHROPIC_API_KEY के शुरू करता है, और पहला API call invalid x-api-key के साथ विफल हो जाता है।
इसका एक आसान समाधान trigger को pull_request_target पर बदलना लग सकता है, जो base repository के context में चलता है और उसे secrets प्राप्त होते हैं। ऐसा यहाँ बिल्कुल न करें। GitHub की अपनी सुरक्षा मार्गदर्शिका कहती है कि ऐसे workflows "privileged होते हैं, जिसका अर्थ है कि वे अन्य privileged workflow triggers के साथ main branch का cache साझा करते हैं, और उन्हें repository में write access तथा referenced secrets तक पहुंच प्राप्त हो सकती है", और इसके परिणाम का "उपयोग repository पर कब्जा करने के लिए किया जा सकता है"।
यही मार्गदर्शिका runner के बारे में स्पष्ट रूप से कहती है: "Self-hosted runners का उपयोग GitHub पर public repositories के लिए लगभग कभी नहीं किया जाना चाहिए, क्योंकि कोई भी उपयोगकर्ता repository के खिलाफ pull requests खोल सकता है और environment को compromise कर सकता है।"
यह दो design विकल्पों को प्रेरित करता है। Job में एक guard लगा है ताकि यह केवल आपकी अपनी repository पर push की गई branches पर ही चले। और workflow में कोई actions/checkout step नहीं है। Agent के पास disk पर कभी भी branch नहीं होती है, इसलिए एक hostile pull request केवल वह text है जिसे model के पास भेजा जाता है। यह आपके VPS पर कोई build script नहीं चला सकता, क्योंकि आपके VPS पर कुछ भी इसे run नहीं करता है।
केवल diff प्राप्त करें, पूरा repository नहीं
एक request से पूरा diff plain text के रूप में प्राप्त हो जाता है।
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 media type वह है जो response को pull request का वर्णन करने वाले JSON object से बदलकर सीधे unified diff में बदल देता है, और gh api उस body को बिना किसी बदलाव के print करता है। आपके द्वारा देखी जाने वाली पहली पंक्ति diff --git a/ से शुरू होनी चाहिए। gh: Not Found (HTTP 404) का अर्थ है कि token repository को नहीं देख सकता है, जिसका अर्थ fine-grained personal token पर लगभग हमेशा यह होता है कि Pull requests permission को सक्षम नहीं किया गया था।
टोकन खर्च करने से पहले फिल्टर करें
यह अनुभाग उस अंतर को स्पष्ट करता है जो एक ऐसे बॉट को बनाता है जिसे लोग पढ़ते हैं और जिसे लोग म्यूट कर देते हैं। नीचे दिए गए प्रत्येक फिल्टर को मॉडल द्वारा एक भी बाइट देखे जाने से पहले चलाया जाता है।
- Path filters. Lock files, vendored directories, minified bundles और generated code को लॉक करें।
package-lock.jsonपर मॉडल की टिप्पणी पूरी तरह से शोर है, और ये फाइलें अक्सर diff में सबसे अधिक बाइट्स लेती हैं। - A size cap. सीमा से अधिक होने पर, समीक्षा छोड़ें और green exit करें। 4,000 लाइन के refactor के लिए साठ अनुमान लगाने के बजाय, एक ईमानदार लाइन लिखें कि यह स्वचालित रूप से समीक्षा करने के लिए बहुत बड़ा था।
- A severity threshold and a comment cap. High और medium findings की रिपोर्ट करें, अधिकतम दस तक, सबसे पहले उच्चतम severity वाली। ग्यारहवीं टिप्पणी कोई नहीं पढ़ता।
The script
इसे /opt/pr-review/review.py के रूप में सेव करें। यह अपना कॉन्फ़िगरेशन environment से पढ़ता है, इसलिए वर्कफ़्लो बिना कोड बदले मॉडल बदल सकता है।
#!/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प्रत्येक फ़ाइल के लिए diff को अलग करना ही path filtering को संभव बनाता है। प्रत्येक लाइन को नंबर देना ही review comments को सही जगह पहुँचाता है। GitHub केवल उसी लाइन पर inline comment स्वीकार करता है जो diff का हिस्सा हो, इसलिए मॉडल को एक वास्तविक लाइन नंबर का उल्लेख करना होता है। उसे नंबर देने का अर्थ है कि वह आविष्कार करने के बजाय एक नंबर कॉपी कर सकता है।
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 header में नंबरिंग होती है। @@ -12,7 +12,9 @@ बताता है कि नई फ़ाइल का hunk लाइन 12 से शुरू होता है, इसलिए काउंटर वहीं से शुरू होता है और केवल जोड़ी गई (added) और अपरिवर्तित (unchanged) लाइनों पर आगे बढ़ता है। हटाई गई (removed) लाइनें बिना नंबर के निकल जाती हैं, क्योंकि वे नई फ़ाइल में मौजूद नहीं होतीं। बैकस्लैश से शुरू होने वाली लाइनों पर लगा गार्ड git द्वारा फ़ाइल के अंत में लिखे गए no-newline मार्कर को छोड़ देता है, जो अन्यथा हर अगली संख्या को एक स्थान आगे खिसका देता।
दोनों exits status 0 का उपयोग करते हैं, 1 का नहीं। एक फ़िल्टर की गई या बहुत बड़ी pull request को हरा चेक (green check) दिखाना चाहिए। जिस लाल चेक (red check) पर इंसान कार्रवाई नहीं कर सकता, उसे अनदेखा कर दिया जाता है, और एक बार जब एक चेक अनदेखा हो जाता है, तो बाकी सभी भी अनदेखे हो जाते हैं।
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,
)उस ब्लॉक में तीन विवरण महत्वपूर्ण हैं। JSON को पहले { और अंतिम } के बीच काटा जाता है क्योंकि मॉडल कभी-कभी अपने उत्तर को code fence में लपेट देता है, और json.loads उस fence के कारण रुक जाता है। path से शुरुआती b/ को हटा दिया जाता है, क्योंकि वह प्रीफ़िक्स diff header से आता है और GitHub को repository-relative पाथ चाहिए होता है। और --input - पूरे रिव्यू को एक API कॉल के रूप में भेजता है, ताकि दस निष्कर्ष दस अलग-अलग नोटिफिकेशन के बजाय एक ही नोटिफिकेशन के रूप में पहुँचें।
जब रिपोर्ट करने के लिए कुछ न हो, तो स्क्रिप्ट कुछ भी पोस्ट नहीं करती। जो बॉट हर pull request पर "no issues found" लिखता है, वह लोगों को उसे अनदेखा करना सिखा देता है, और फिर वे उस नोटिफिकेशन को भी अनदेखा कर देते हैं जो वास्तव में महत्वपूर्ण होता है।
इसे वर्कफ़्लो से जोड़ें
इसे .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.pyGITHUB_REPOSITORY उस env: ब्लॉक में नहीं है क्योंकि Actions इसे हर जॉब के लिए पहले से ही सेट कर देता है। concurrency ग्रुप बिल के लिए महत्वपूर्ण है: इसके बिना, एक ब्रांच पर तीन त्वरित सुधार (quick fixes) पुश करने से तीन पूर्ण रिव्यू चलते हैं और आपको तीनों के लिए भुगतान करना पड़ता है, जबकि इसके साथ केवल अंतिम वाला ही रहता है।
if: लाइन दो काम करती है। पहला भाग फोर्क (forks) से आने वाले पुल रिक्वेस्ट को स्किप करता है, जो वैसे भी बिना की (key) के विफल हो जाते। दूसरा भाग आपकी टीम को एक ऑफ स्विच देता है: पुल रिक्वेस्ट में no-ai-review लेबल जोड़ें और जॉब नहीं चलेगी।
एक पुल रिक्वेस्ट खोलें और देखें कि क्या होता है:
gh run list --workflow=pr-review.yml --limit 3
gh run view --log
gh pr view 42 --commentsएक रन जो कुछ सेकंड में लॉग में nothing above the severity threshold; posting no comment के साथ समाप्त होता है, वह सही ढंग से काम कर रहा है। एक छोटे, साफ पुल रिक्वेस्ट पर यही अपेक्षित परिणाम है।
FAQ
स्वचालित पुल रिक्वेस्ट रिव्यू की लागत क्या है?
डिफ (diff) ही इनपुट का मुख्य हिस्सा होता है, इसलिए डिफ का आकार ही कीमत निर्धारित करता है। नीचे 500 लाइनों का एक डिफ और सिस्टम प्रॉम्प्ट दिया गया है, जिसे अनुमानित करने के बजाय टोकन काउंटिंग एंडपॉइंट का उपयोग करके मापा गया है।
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"
}
]यह डिफ Haiku 4.5 पर 8,000 इनपुट टोकन और Sonnet 5 पर 10,400 टोकन का था। टेक्स्ट समान है, लेकिन गणना अलग है। 4.7 के बाद के Claude मॉडल एक नए टोकेनाइज़र का उपयोग करते हैं जो समान इनपुट के लिए लगभग 30% अधिक टोकन उत्पन्न करता है, जिसका दस्तावेजीकरण Anthropic ने अपने मूल्य निर्धारण पृष्ठ पर किया है। जब भी आप प्रति मिलियन टोकन की कीमत के आधार पर किसी नए मॉडल की तुलना पुराने मॉडल से करें, तो इस बात का ध्यान रखें।
अगस्त 2026 तक की सूची मूल्य: Haiku 4.5 के लिए $1 प्रति मिलियन इनपुट टोकन और $5 प्रति मिलियन आउटपुट टोकन है। Sonnet 5 की शुरुआती कीमत 31 अगस्त 2026 तक $2 और $10 है, उसके बाद यह $3 और $15 होगी। Opus 5 की कीमत $5 और $25 है।
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"
}
]यह Haiku 4.5 पर प्रति पुल रिक्वेस्ट 1.4 सेंट और Opus 5 पर 9.1 सेंट है। एक टीम जो महीने में 200 पुल रिक्वेस्ट मर्ज करती है, उसे Haiku 4.5 पर लगभग $2.80, Sonnet 5 पर $7.28, या Opus 5 पर $18.20 का भुगतान करना होगा। 1 सितंबर 2026 से, Sonnet 5 की पंक्ति को 1.5 से गुणा करें।
दो चीजें वास्तविक बिल को इस अनुमान से ऊपर ले जाती हैं। synchronize हर पुश पर रिव्यू ट्रिगर करता है, इसलिए आठ पुश वाली एक सक्रिय ब्रांच की लागत आठ रिव्यू के बराबर होती है, और कॉन्करेंसी नियम केवल तभी मदद करता है जब पुश एक-दूसरे के करीब आते हैं। ये आंकड़े यह भी मानते हैं कि पाथ फिल्टर काम कर रहे हैं: एक अनफिल्टर्ड लॉक फाइल अपने आप में इनपुट को दोगुना कर सकती है।
प्रॉम्प्ट कैशिंग यहाँ मदद नहीं करती है। कैश किए गए प्रीफिक्स को कॉल के बीच बाइट-दर-बाइट समान होना चाहिए, और डिफ हर बार अलग होता है। सिस्टम प्रॉम्प्ट ही एकमात्र स्थिर हिस्सा है, और यह न्यूनतम कैश योग्य लंबाई से काफी नीचे है। सामान्य नियम के लिए, प्रॉम्प्ट कैशिंग कब फायदेमंद होती है देखें, और ऊपर दिए गए तीन मॉडलों में से चुनने के लिए, किस काम के लिए कौन सा Claude मॉडल उपयोग करें देखें।
इसे चालू करने से पहले अपने डिफ को मापें
payload के बनने के बाद यह लाइन जोड़ें, फिर पिछले महीने की कुछ पुल रिक्वेस्ट पर हाथ से स्क्रिप्ट चलाएं:
print(client.messages.count_tokens(
model=MODEL, system=SYSTEM, messages=[{"role": "user", "content": payload}]
).input_tokens)काउंट एंडपॉइंट मॉडल को रन नहीं करता है, इसलिए यह इनपुट या आउटपुट टोकन का उपभोग नहीं करता है, और यह आपके द्वारा नामित मॉडल के टोकेनाइज़र का उपयोग करता है। इसे अपनी रिपॉजिटरी की दस वास्तविक पुल रिक्वेस्ट पर चलाएं और औसत (mean) के बजाय माध्यिका (median) लें, ताकि एक विशाल माइग्रेशन अनुमान को विकृत न करे।
Review bots को mute क्यों किया जाता है, और इससे कैसे बचें
दो व्यवहार इन bots पर भरोसा खत्म कर देते हैं, और ऊपर दिए गए कोड में दोनों का समाधान मौजूद है।
एक साथ सब कुछ review करना। जो bot चालीस comments छोड़ता है, उनमें से एक भी नहीं पढ़ा जाता। Severity threshold और दस comment की सीमा शिष्टाचार नहीं है, बल्कि ये वे उपाय हैं जो वास्तविक findings को दृश्यमान रखते हैं। Truncate करने से पहले severity के आधार पर sort करने का मतलब है कि सीमा सबसे कम महत्वपूर्ण findings को हटाती है, न कि किन्हीं यादृच्छिक दस को।
ऐसी चीज़ पर विश्वास के साथ comment करना जिसे वह जांच नहीं सकता। यही वह कारण है जिसके चलते engineers इसे हमेशा के लिए बंद कर देते हैं। 40,000 lines के codebase की 200 lines देखने वाला model भी यह लिख देगा कि "यह redis_client.py में cache invalidation को तोड़ता है" उस file के बारे में जिसे उसने कभी देखा ही नहीं। System prompt इसे स्पष्ट भाषा में रोकता है: केवल दिखाई गई lines में मौजूद defects की रिपोर्ट करें, और किसी भी ऐसी चीज़ को छोड़ दें जिसके बारे में आप निश्चित नहीं हैं। विफलता का सीधे नाम लेना, सामान्य रूप से सटीकता मांगने से बेहतर काम करता है, और model को यह बताना कि empty result सामान्य है, उसे दो lines के बदलाव पर कुछ भी मनगढ़ंत कहने से रोकता है।
Review को COMMENT के रूप में post करें, न कि REQUEST_CHANGES के रूप में। Model की राय merge को block करने में सक्षम नहीं होनी चाहिए, और जिस क्षण ऐसा होता है, deadline वाला कोई भी व्यक्ति उससे बहस करने के बजाय पूरे workflow को हटा देगा।
विफलता के प्रकार और दिखाई देने वाले स्ट्रिंग्स
समीक्षा पोस्ट करते समय HTTP 422 त्रुटि। gh, gh: Unprocessable Entity (HTTP 422) प्रिंट करता है और रिस्पॉन्स बॉडी में फील्ड का नाम होता है: Pull request review thread line must be part of the diff। GitHub उस टिप्पणी को एंकर नहीं कर पाता है। इसके सामान्य कारण हैं: मॉडल द्वारा बनाई गई लाइन संख्या, एक path जिसमें अभी भी b/ प्रीफिक्स है, या हटाई गई लाइन पर टिप्पणी, जिसके लिए side को RIGHT के बजाय LEFT पर सेट करना आवश्यक है। पोस्ट करने से पहले समीक्षा JSON को प्रिंट करें और diff के मुकाबले एक टिप्पणी की मैन्युअल जांच करें।
मॉडल API से invalid x-api-key। यह चरण पहले messages.create कॉल पर विफल हो जाता है। या तो रिपॉजिटरी पर ANTHROPIC_API_KEY सीक्रेट सेट नहीं है, या पुल रिक्वेस्ट किसी फोर्क से आई है, इसलिए Actions ने कोई सीक्रेट पास नहीं किया। if: लाइन में मौजूद फोर्क गार्ड को इसे स्किप कर देना चाहिए था, इसलिए पहले उस लाइन की जांच करें।
gh: Resource not accessible by integration (HTTP 403)। जॉब टोकन पुल रिक्वेस्ट में लिख नहीं सकता है। permissions: ब्लॉक में pull-requests: write जोड़ें। यदि यह पहले से वहां है, तो Settings, फिर Actions, और फिर General देखें, जहां एक ऑर्गेनाइजेशन पॉलिसी किसी भी वर्कफ़्लो टोकन द्वारा किए जा सकने वाले अनुरोधों को सीमित कर सकती है।
json.decoder.JSONDecodeError। मॉडल ने पार्स करने योग्य JSON नहीं लौटाया। इसका सामान्य कारण वह रिस्पॉन्स है जो टोकन की सीमा तक पहुंच गया और ऑब्जेक्ट के बीच में ही रुक गया। लॉग लाइन इसके लिए ठीक यही प्रिंट करती है: stop_reason। max_tokens का मान होने का मतलब है कि max_tokens को बढ़ाएं या MAX_COMMENTS को कम करें।
वर्कफ़्लो कभी नहीं चलता। gh run list पुल रिक्वेस्ट के लिए कुछ नहीं दिखाता है। जांचें कि paths-ignore ने प्रत्येक बदली हुई फाइल को फ़िल्टर तो नहीं कर दिया, फिर फोर्क गार्ड और लेबल गार्ड की जांच करें, और अंत में यह देखें कि क्या VPS पर sudo systemctl status 'actions.runner.*' के साथ रनर सक्रिय है। एक ऑफलाइन रनर जॉब को कतार में छोड़ देता है और पुल रिक्वेस्ट में कहीं भी कोई त्रुटि संदेश नहीं दिखता है।
हर समीक्षा खाली वापस आती है। एक रन के लिए MIN_SEVERITY को low पर सेट करें। यदि निष्कर्ष दिखाई देते हैं, तो थ्रेशोल्ड अपना काम कर रहा है। यदि कुछ भी दिखाई नहीं देता है, तो payload को प्रिंट करें और पुष्टि करें कि फ़िल्टर ने पूरे diff को हटाया नहीं है।
अपने अन्य agents के साथ इसे चलाना
Reviewer छोटा है, इसलिए इसे उस सर्वर पर डालना आकर्षक लगता है जहाँ पहले से ही सब कुछ चल रहा है। यदि repository महत्वपूर्ण है, तो इसे अलग रखें। यह process एक ऐसा token रखती है जो आपके code पर comment कर सकता है और एक ऐसी key रखती है जो आपके पैसे खर्च कर सकती है। एक self-hosted runner को इसी तरह डिज़ाइन किया गया है कि वहाँ workflow code execute होता है। बिना sudo अधिकारों वाला एक समर्पित unprivileged account, ऐसे host पर जिस पर कुछ और न चल रहा हो, न्यूनतम सुरक्षा मानक है। यदि आप ऐसे interactive agents भी चलाते हैं जो code checkout करते हैं, तो प्रति agent एक disposable VM वह pattern है जो सुरक्षित रहता है, और VPS पर coding agent चलाना सामान्य setup को कवर करता है। यदि Anthropic API आपके लिए नया है, तो VPS पर पहला Claude API app इससे शुरू करने के लिए एक छोटा विकल्प है।
FAQ
क्या AI PR review agent को मेरे repository पर write access की आवश्यकता है?
नहीं। इसे review पोस्ट करने के लिए pull-requests: write और diff प्राप्त करने के लिए contents: read की आवश्यकता होती है। यह पूरी सूची है, और आप इसे workflow के permissions: ब्लॉक में सेट करते हैं, जो यह सीमित करता है कि प्रति-जॉब GITHUB_TOKEN क्या कर सकता है। इन दो लाइनों के साथ, agent pull request पर टिप्पणी कर सकता है लेकिन commit push या branch merge नहीं कर सकता। Review को REQUEST_CHANGES के बजाय event: COMMENT के साथ पोस्ट करें ताकि यह merge को ब्लॉक न कर सके।
मेरा review comment HTTP 422 के साथ विफल क्यों हो जाता है?
GitHub केवल उसी लाइन पर inline review comment स्वीकार करता है जो pull request diff का हिस्सा हो, और ऐसा न होने पर Pull request review thread line must be part of the diff लौटाता है। जाँचें कि path repository-relative है और इसमें diff header से कोई b/ prefix नहीं है, और यह कि लाइन नंबर उस फाइल के hunk के भीतर दिखाई देता है। side को जोड़ी गई या अपरिवर्तित लाइन के लिए RIGHT और हटाई गई लाइन के लिए LEFT होना चाहिए। मॉडल को भेजने से पहले diff की प्रत्येक लाइन को उसके new-file लाइन नंबर के साथ prefix करना ही वह तरीका है जो मॉडल को गलत नंबर बनाने से रोकता है।
क्या मैं इसे forks से pull requests वाले public repository पर चला सकता हूँ?
इस डिज़ाइन के साथ नहीं। GitHub fork से ट्रिगर हुए workflow को secrets नहीं देता है, इसलिए मॉडल की key गायब रहती है और run विफल हो जाता है। GitHub यह भी कहता है कि self-hosted runners का उपयोग "public repositories के लिए लगभग कभी नहीं किया जाना चाहिए", क्योंकि कोई भी व्यक्ति ऐसी pull request खोल सकता है जो आपकी मशीन पर कोड चला दे। Public project के लिए, या तो reviewer को केवल repository पर push की गई branches तक सीमित रखें, जो कि if: गार्ड करता है, या review step को GitHub-hosted runner पर ले जाएं और यह स्वीकार करें कि diff आपके स्वयं के infrastructure से बाहर जा रहा है।
मुझे pull request review के लिए किस मॉडल का उपयोग करना चाहिए?
Haiku 4.5 से शुरुआत करें। दोष के प्रकारों की एक निश्चित सूची के विरुद्ध सीमित diff को पढ़ना कोई कठिन तर्क समस्या नहीं है, और सबसे सस्ता मॉडल मासिक बिल को ऐसी संख्या पर रखता है जिस पर कोई बहस नहीं करता। यदि आप पाते हैं कि यह आपकी भाषा या framework में वास्तविक bugs को छोड़ रहा है, तो Sonnet 5 पर जाएं, और इसे मान लेने के बजाय मापें। Opus 5 प्रति pull request तीनों में सबसे महंगा है, जिसे हर feature branch के हर push की तुलना में release branch पर justify करना आसान है।