VPS पर AI PR review agent कैसे सेटअप करें
अपने VPS पर AI PR review agent सेटअप करने का पूरा तरीका जानें। इसमें self-hosted runner, diff-scoped prompts, path filters और inline comments का उपयोग करना शामिल है।
Self-hosted PR review agent क्या करता है
Self-hosted PR review agent आपके अपने server पर चलने वाला एक छोटा program है। यह pull request (PR) का diff पढ़ता है और केवल बदली हुई lines model को भेजता है। वापस मिलने वाला output inline review comments के रूप में post किया जाता है। यह आपकी branch को कभी checkout नहीं करता और pull request द्वारा न बदली गई किसी file को कभी नहीं पढ़ता। इसके पास केवल एक model API (application programming interface) key और ऐसा एक token होता है जो comments कर सकता है, इसके अलावा कुछ नहीं। Agent की यह सीमित परिभाषा उसे किसी autonomous system की तुलना में filters के आगे रखे गए prompt के अधिक करीब बनाती है। यदि इसे बनाने से पहले आप पूरी तस्वीर देखना चाहते हैं, तो concepts से स्वयं लिखे गए loop तक का staged path इसके आधारभूत हिस्सों को समझाता है।
एक 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, जिसमें self-hosted GitHub Actions runner पहले से ही repository के साथ registered हो। इसे register करते समय
pr-reviewlabel दें, क्योंकि नीचे दिया गया workflow इसी label के आधार पर चयन करता है। - Claude Console से प्राप्त एक Anthropic API key।
- एक ऐसी repository जहाँ आप यह नियंत्रित कर सकें कि pull request कौन खोल सकता है। Private repository के मामले में यह आसान है। नीचे दिया गया fork section public repository के मामले को कवर करता है, और वहां इसका समाधान थोड़ा जटिल है।
VPS पर reviewer इंस्टॉल करें
Runner service उस unprivileged account के रूप में चलती है जिसे आपने ./svc.sh install चलाते समय बनाया था। Reviewer को उसी account के अंतर्गत इंस्टॉल करें ताकि job बिना sudo के इसे execute कर सके। नीचे दिए गए runner को अपने account name से बदलें।
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 चलाएं और पुनः प्रयास करें।
कुंजी और टोकन कहाँ रहते हैं
दो सीक्रेट्स, दो अलग-अलग जीवनकाल। इनमें से कोई भी रिपॉजिटरी में नहीं जाना चाहिए।
ANTHROPIC_API_KEY एक रिपॉजिटरी सीक्रेट है, जिसे Settings, फिर Secrets and variables, और उसके बाद Actions के अंतर्गत सेट किया जाता है। GitHub इसे एन्क्रिप्ट करता है और रन-टाइम पर स्टेप के एनवायरनमेंट में इंजेक्ट करता है। यह कभी भी डिस्क पर कोई फाइल नहीं होती और न ही कभी git हिस्ट्री में रहती है।
GITHUB_TOKEN अलग तरह से काम करता है। Actions प्रत्येक जॉब के लिए एक नया टोकन बनाता है और जॉब समाप्त होने पर उसे नष्ट कर देता है। वह टोकन क्या कर सकता है, यह वर्कफ़्लो में permissions: ब्लॉक द्वारा निर्धारित किया जाता है, इसलिए यहीं पर वास्तव में 'लीस्ट प्रिविलेज' (न्यूनतम विशेषाधिकार) लागू होता है:
permissions:
contents: read
pull-requests: writeवह टोकन एक रिव्यू पोस्ट कर सकता है। वह कमिट पुश नहीं कर सकता, ब्रांच मर्ज नहीं कर सकता, वर्कफ़्लो फ़ाइल को एडिट नहीं कर सकता, या किसी अन्य रिपॉजिटरी को नहीं छू सकता। जो एजेंट कमेंट कर सकता है, वह एक रिव्यूअर है। जो एजेंट पुश कर सकता है, वह एक कमिटर है, और इसके लिए किसी ने सहमति नहीं दी थी। मॉडल कुंजी (model key) के साथ भी उतनी ही सावधानी बरतें, क्योंकि यह आपके अकाउंट से पैसे खर्च करती है। इस प्रकार की समस्या के बारे में अधिक जानकारी AI एजेंट की पहुँच से सीक्रेट्स को दूर रखना में दी गई है।
Actions जॉब लॉग्स में सटीक सीक्रेट स्ट्रिंग को *** से बदल देता है। यह केवल सटीक स्ट्रिंग का मिलान करता है, इसलिए यदि आप किसी कुंजी को base64 एन्कोड करते हैं, दो लाइनों में विभाजित करते हैं, या एक बार में एक कैरेक्टर प्रिंट करते हैं, तो वह स्पष्ट रूप से दिखाई देगी। ऐसा कोई डीबग स्टेप न जोड़ें जो एनवायरनमेंट को डंप करता हो।
फोर्क से आने वाला पुल रिक्वेस्ट आपका API key क्यों नहीं देख पाता
GitHub का नियम संक्षिप्त है: GITHUB_TOKEN के अपवाद को छोड़कर, जब कोई वर्कफ़्लो किसी फोर्क की गई रिपॉजिटरी से ट्रिगर होता है, तो secrets को रनर तक नहीं भेजा जाता है। इसलिए, फोर्क से चलाया गया pull_request आपके स्क्रिप्ट को बिना किसी ANTHROPIC_API_KEY के शुरू करता है, और पहला API कॉल invalid x-api-key के साथ विफल हो जाता है।
इसका लुभावना समाधान ट्रिगर को pull_request_target में बदलना है, जो बेस रिपॉजिटरी के संदर्भ में चलता है और उसे secrets प्राप्त हो जाते हैं। यहाँ ऐसा न करें। GitHub का अपना सुरक्षा मार्गदर्शन कहता है कि ऐसे वर्कफ़्लो "विशेषाधिकार प्राप्त (privileged) होते हैं, जिसका अर्थ है कि वे अन्य विशेषाधिकार प्राप्त वर्कफ़्लो ट्रिगर्स के साथ मुख्य ब्रांच के कैश को साझा करते हैं, और उनके पास रिपॉजिटरी राइट एक्सेस और संदर्भित secrets तक पहुंच हो सकती है", और यह कि परिणाम का "उपयोग रिपॉजिटरी पर कब्जा करने के लिए किया जा सकता है"।
यही मार्गदर्शन रनर के बारे में स्पष्ट है: "GitHub पर सार्वजनिक रिपॉजिटरी के लिए self-hosted runners का उपयोग लगभग कभी नहीं किया जाना चाहिए, क्योंकि कोई भी उपयोगकर्ता रिपॉजिटरी के खिलाफ पुल रिक्वेस्ट खोल सकता है और वातावरण (environment) से समझौता कर सकता है।"
यह दो डिज़ाइन विकल्पों को प्रेरित करता है। जॉब में एक गार्ड लगा है ताकि यह केवल आपकी अपनी रिपॉजिटरी पर पुश की गई ब्रांच पर ही चले। और वर्कफ़्लो में कोई actions/checkout स्टेप बिल्कुल नहीं है। एजेंट के पास डिस्क पर कभी भी ब्रांच नहीं होती है, इसलिए एक शत्रुतापूर्ण पुल रिक्वेस्ट केवल टेक्स्ट है जिसे एक मॉडल को भेजा जाता है। यह आपके VPS पर कोई बिल्ड स्क्रिप्ट नहीं चला सकता, क्योंकि आपके VPS पर कुछ भी इसे रन नहीं करता है। हालाँकि, टेक्स्ट का मतलब हानिरहित नहीं है: किसी अजनबी द्वारा लिखा गया diff एक मॉडल तक पहुंचने वाला अविश्वसनीय इनपुट है, वही ट्रस्ट बाउंड्री जिसका सामना आप तब करते हैं जब आप एजेंट को वेब सर्च करने की अनुमति देते हैं, और यहाँ इसे नियंत्रित करने वाली एकमात्र चीज यह है कि यह एजेंट केवल एक टिप्पणी पोस्ट करने के अलावा कुछ नहीं कर सकता है।
पूरे रिपॉजिटरी के बजाय केवल diff प्राप्त करें
एक अनुरोध पूरे 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 रिपॉजिटरी को नहीं देख सकता है, जिसका अर्थ fine-grained personal token पर लगभग हमेशा यह होता है कि Pull requests permission को सक्षम नहीं किया गया था।
टोकन खर्च करने से पहले फिल्टर करें
यह अनुभाग उस अंतर को दर्शाता है जो एक ऐसे बॉट के बीच होता है जिसे लोग पढ़ते हैं और जिसे लोग म्यूट कर देते हैं। नीचे दिया गया प्रत्येक फिल्टर मॉडल द्वारा एक भी बाइट देखने से पहले चलता है।
- Path filters. लॉक फाइलें, वेंडर्ड डायरेक्टरी, मिनिफ़ाइड बंडल और जनरेटेड कोड को लॉक करें।
package-lock.jsonपर मॉडल की टिप्पणी पूरी तरह से शोर है, और ये फाइलें अक्सर diff में सबसे अधिक बाइट्स लेती हैं। - A size cap. सीमा से अधिक होने पर, समीक्षा छोड़ें और सफलतापूर्वक बाहर निकलें। 4,000 लाइनों के रिफैक्टर के लिए साठ अनुमान लगाने के बजाय, केवल एक ईमानदार लाइन लिखें कि यह स्वचालित समीक्षा के लिए बहुत बड़ा था।
- A severity threshold and a comment cap. उच्च और मध्यम स्तर की फाइंडिंग्स की रिपोर्ट करें, अधिकतम दस तक, सबसे पहले उच्चतम गंभीरता वाली। ग्यारहवीं टिप्पणी कोई नहीं पढ़ता।
स्क्रिप्ट
इसे /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 केवल उसी लाइन पर इनलाइन कमेंट स्वीकार करता है जो 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 हेडर में नंबरिंग होती है। @@ -12,7 +12,9 @@ बताता है कि नई फ़ाइल का hunk लाइन 12 से शुरू होता है, इसलिए काउंटर वहीं से शुरू होता है और केवल जोड़ी गई और अपरिवर्तित लाइनों पर आगे बढ़ता है। हटाई गई लाइनें बिना नंबर के गुजर जाती हैं, क्योंकि वे नई फ़ाइल में मौजूद नहीं होतीं। बैकस्लैश से शुरू होने वाली लाइनों पर लगा गार्ड 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 को पहले { और अंतिम } के बीच काटा जाता है क्योंकि मॉडल कभी-कभी अपने उत्तर को कोड फेंस में लपेट देता है, और json.loads उस फेंस के कारण अटक जाता है। path से शुरुआती b/ को हटा दिया जाता है, क्योंकि वह प्रीफ़िक्स diff हेडर से आता है और 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) से आने वाले pull requests को छोड़ देता है, जो वैसे भी बिना की (key) के विफल हो जाते। दूसरा भाग आपकी टीम को एक ऑफ स्विच देता है: pull request में no-ai-review लेबल जोड़ें और जॉब रन नहीं होगी।
एक pull request खोलें और देखें कि क्या होता है:
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 के साथ लॉग में समाप्त हो जाता है, वह सही ढंग से काम कर रहा है। एक छोटे, साफ pull request पर यही अपेक्षित परिणाम है।
स्वचालित पुल रिक्वेस्ट रिव्यू की लागत क्या है?
Diff ही लगभग पूरा इनपुट होता है, इसलिए diff का आकार ही कीमत निर्धारित करता है। नीचे 500 लाइन का एक मापा गया diff और सिस्टम प्रॉम्प्ट दिया गया है, जिसे अनुमानित करने के बजाय टोकन काउंटिंग एंडपॉइंट के साथ गिना गया है।
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 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 हर पुश पर रिव्यू ट्रिगर करता है, इसलिए आठ पुश वाली एक सक्रिय ब्रांच की लागत आठ रिव्यू के बराबर होती है, और कॉन्करेंसी नियम केवल तब मदद करता है जब पुश एक-दूसरे के करीब आते हैं। ये आंकड़े यह भी मानते हैं कि पाथ फिल्टर काम कर रहे हैं: एक अनफिल्टर्ड लॉक फाइल अपने आप में इनपुट को दोगुना कर सकती है।
प्रॉम्प्ट कैशिंग यहाँ मदद नहीं करती है। कैश किए गए प्रीफिक्स को कॉल के बीच बाइट-समान होना चाहिए, और diff हर बार अलग होता है। सिस्टम प्रॉम्प्ट ही एकमात्र स्थिर हिस्सा है, और यह न्यूनतम कैश योग्य लंबाई से काफी नीचे है। सामान्य नियम के लिए, जब प्रॉम्प्ट कैशिंग खुद का खर्च निकाल लेती है देखें, और ऊपर दिए गए तीन मॉडल्स में से चुनने के लिए, किस काम के लिए कौन सा Claude मॉडल उपयोग करें देखें।
इसे चालू करने से पहले अपने diffs को मापें
payload बनने के बाद यह लाइन जोड़ें, फिर पिछले महीने की कुछ पुल रिक्वेस्ट के खिलाफ मैन्युअल रूप से स्क्रिप्ट चलाएं:
print(client.messages.count_tokens(
model=MODEL, system=SYSTEM, messages=[{"role": "user", "content": payload}]
).input_tokens)काउंट एंडपॉइंट मॉडल को रन नहीं करता है, इसलिए यह इनपुट या आउटपुट टोकन का उपभोग नहीं करता है, और यह आपके द्वारा नामित मॉडल से संबंधित टोकनाइज़र का उपयोग करता है। इसे अपनी रिपॉजिटरी से दस वास्तविक पुल रिक्वेस्ट पर चलाएं और औसत के बजाय माध्यिका (median) लें, ताकि एक विशाल माइग्रेशन अनुमान को विकृत न करे।
रिव्यू बॉट्स को म्यूट क्यों किया जाता है, और इससे कैसे बचें
दो व्यवहार इन बॉट्स के प्रति भरोसे को खत्म करते हैं, और ऊपर दिए गए कोड में दोनों का समाधान मौजूद है।
एक साथ सब कुछ रिव्यू करना। जो बॉट चालीस टिप्पणियाँ (comments) छोड़ता है, उनमें से एक भी नहीं पढ़ी जाती। गंभीरता की सीमा (severity threshold) और दस टिप्पणियों की सीमा शिष्टाचार नहीं है, बल्कि यही वह तरीका है जिससे वास्तविक निष्कर्ष दिखाई देते हैं। ट्रंकेट करने से पहले गंभीरता के आधार पर सॉर्ट करने का मतलब है कि सीमा तय करने पर सबसे कम महत्वपूर्ण निष्कर्ष हटेंगे, न कि कोई भी रैंडम दस।
ऐसी चीज़ पर आत्मविश्वास के साथ टिप्पणी करना जिसे वह चेक नहीं कर सकता। यही वह व्यवहार है जिसके कारण इंजीनियर इसे हमेशा के लिए बंद कर देते हैं। एक मॉडल जिसे 40,000 लाइनों के कोडबेस में से 200 लाइनें दिखाई जाती हैं, वह फिर भी "यह redis_client.py में कैश इनवैलिडेशन को तोड़ता है" जैसी बात उस फाइल के बारे में लिख देगा जिसे उसने कभी देखा ही नहीं। सिस्टम प्रॉम्प्ट इसे सरल भाषा में रोकता है: केवल उन दोषों की रिपोर्ट करें जो दिखाई गई लाइनों में स्पष्ट हैं, और किसी भी ऐसी चीज़ को छोड़ दें जिसके बारे में आप निश्चित नहीं हैं। विफलता का सीधे नाम लेना, सामान्य रूप से सटीकता के लिए कहने से बेहतर काम करता है, और मॉडल को यह बताना कि खाली परिणाम आना सामान्य है, उसे दो लाइन के बदलाव पर कुछ भी मनगढ़ंत बोलने से रोकता है। जो दोष केवल पूरे रिपॉजिटरी में दिखाई देते हैं, वे एक अलग काम हैं, और सुरक्षा स्कैनिंग के लिए self-hosting open-kritt उस दायरे को कवर करने का एक तरीका है, बिना इस रिव्यूअर की दृश्यता को बढ़ाए।
रिव्यू को COMMENT के रूप में पोस्ट करें, कभी भी REQUEST_CHANGES के रूप में नहीं। किसी मॉडल की राय को मर्ज को ब्लॉक करने में सक्षम नहीं होना चाहिए, और जिस क्षण वह ऐसा कर सकता है, डेडलाइन वाला कोई भी व्यक्ति उससे बहस करने के बजाय पूरे वर्कफ़्लो को ही हटा देगा। जहाँ आप चाहते हैं कि किसी एजेंट का निर्णय वास्तव में मायने रखे, वहां उसे ऐसा कुछ बनाने के लिए कहें जिसे आप चेक कर सकें, न कि ऐसा कुछ जिस पर आपको विश्वास करना पड़े। यही वह विचार है जो एजेंट द्वारा आपको एक एविडेंस रिपोर्ट देने, जिसे आप स्वयं दोबारा चला सकें के पीछे है।
विफलता के प्रकार और उनसे संबंधित संदेश
समीक्षा पोस्ट करते समय 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 का उपयोग करना सबसे बेहतर तरीका है, और VPS पर coding agent चलाना सामान्य सेटअप को कवर करता है। यदि 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 नहीं कर सकता। समीक्षाओं को REQUEST_CHANGES के बजाय event: COMMENT के साथ पोस्ट करें ताकि यह merge को ब्लॉक न कर सके।
मेरी review टिप्पणी HTTP 422 के साथ विफल क्यों हो जाती है?
GitHub केवल उसी लाइन पर inline review टिप्पणी स्वीकार करता है जो pull request diff का हिस्सा है, और ऐसा न होने पर Pull request review thread line must be part of the diff लौटाता है। जाँचें कि path repository-relative है, जिसमें diff हेडर से कोई b/ उपसर्ग नहीं है, और लाइन नंबर उस फ़ाइल के hunk के भीतर दिखाई देता है। side को जोड़ी गई या अपरिवर्तित लाइन के लिए RIGHT और हटाई गई लाइन के लिए LEFT होना चाहिए। मॉडल को भेजने से पहले diff की प्रत्येक लाइन को उसके new-file लाइन नंबर के साथ उपसर्ग करना ही वह तरीका है जो मॉडल को मनमाने नंबर बनाने से रोकता है।
क्या मैं इसे 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 करना आसान है।