View on GitHub
The pipeline

From webhook to review

What happens between the moment a developer pushes to a PR and the moment they see PrSentry's comments. Every step, with the actual code.

01

Webhook received

POST /webhooks/github → ~2 ms
GitHub fires a pull_request event the moment a PR is opened, synchronized, or reopened. We respond 200 immediately and process the work in a background queue.
python  ·  webhooks.py
@app.post(class="tok-str">"/webhooks/github")
async def gh_webhook(req: Request, bg: BackgroundTasks):
    payload = await req.body()
    verify_signature(payload, req.headers[class="tok-str">"x-hub-signature-256"])
    event = req.headers[class="tok-str">"x-github-event"]
    if event == class="tok-str">"pull_request":
        bg.add_task(enqueue_review, json.loads(payload))
    return {class="tok-str">"ok": True}
02

Signature validated

HMAC-SHA256 · constant-time compare
Every webhook is signed with your GitHub App secret. We verify the signature in constant time before doing anything else. Mismatched signatures are dropped.
python  ·  security.py
def verify_signature(body: bytes, header: str) -> None:
    expected = class="tok-str">"sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, header):
        raise HTTPException(401, class="tok-str">"bad signature")
03

Diff fetched

GET /repos/{o}/{r}/pulls/{n}.diff
We pull the unified diff using the installation token, parse it into per-file hunks, and capture the head SHA so comments anchor to the right commit.
04

Files filtered

Glob match · ~25 patterns
Lockfiles, vendor dirs, generated code, and binaries get dropped before any token is spent. Patterns are configurable per-repo.
yaml  ·  .prsentry.yml
skip:
  - class="tok-str">"**/package-lock.json"
  - class="tok-str">"**/yarn.lock"
  - class="tok-str">"**/poetry.lock"
  - class="tok-str">"**/uv.lock"
  - class="tok-str">"**/*.min.js"
  - class="tok-str">"**/dist/**"
  - class="tok-str">"**/build/**"
  - class="tok-str">"**/node_modules/**"
  - class="tok-str">"**/__generated__/**"
  - class="tok-str">"**/*.{png,jpg,gif,pdf,zip}"
05

Token budget applied

per-file: 8k · per-PR: 64k
Files are sorted by relevance and truncated to fit within the per-file ceiling. The PR-wide budget is a hard stop — we never exceed it.
06

Agent runs

LangGraph · Claude Sonnet 4.5
A LangGraph agent orchestrates the review. The graph is intentionally simple: reviewer reads, calls tools, collects findings, and exits. ruff_check returns real linter output Claude can cite.
START
reviewer
↑↓
tools
collect
END
python  ·  prompts.py — system prompt
You are PrSentry, a senior code reviewer.

You review pull request diffs with surgical precision.
Output inline review comments anchored to specific
lines in the diff. Each comment has:

  - severity: CRITICAL | WARNING | SUGGESTION | NITPICK
  - line:     1-indexed position in the new file
  - body:     concise, actionable, with a code suggestion
              when possible

Rules:
  1. Cite ruff findings when they exist; do not duplicate.
  2. CRITICAL is reserved for security issues, data loss,
     or runtime crashes — not style.
  3. Skip lockfiles, generated code, and binary blobs.
  4. If the diff is clean, say so in a single summary
     comment. Do not invent issues.

You have tools: ruff_check, read_full_file, get_pr_metadata.
Use them liberally. Stop when you have enough signal.
07

Review posted

POST /repos/{o}/{r}/pulls/{n}/reviews
Findings are batched into a single GitHub review (one API call) so the PR timeline stays clean. Inline comments anchor to the right side of the diff.
08

Run saved to DB

INSERT INTO runs · Postgres
Status, duration, file count, comment count, token usage, and any errors are persisted. The Langfuse trace ID is stored alongside so you can jump straight to the agent transcript.