How it works
A small, auditable pipeline around one review engine.
The hosted control plane does four things: receive webhooks, queue jobs, run the CLI, and store the result. Everything that decides what to say about your code is in the open-source binary. The same pipeline answers @postil mentions on PRs and issues (GitHub only): it reviews and answers only, and never opens PRs or pushes commits.
step 1
Webhook
GitHub delivers a pull_request event. The signature is verified against the raw body before parsing; deliveries are deduped by id. Drafts and disabled repos are dropped here.
step 2
Queue
A review job lands in a Postgres-native queue (FOR UPDATE SKIP LOCKED). Webhooks can kick a bounded drain immediately, while the worker remains a slow fallback; retries use exponential backoff, and a watchdog reclaims anything stuck.
step 3
CLI
The worker mints a short-lived installation token, creates both check-runs, and shells out to the same pinned postil binary you can run locally. All review logic lives in the CLI.
step 4
Check-runs
The CLI posts inline comments in one batched review and completes postil/review. After the envelope is stored, the control plane completes postil/gate according to the organization’s merge-gate setting. The diff is not stored.
webhook → queue → CLI → check-runs. The worker owns the check-run ids from the start, so even a crashed review completes as failed instead of hanging in_progress.
01 — Doctrine
Advisory by default. Blocking when enabled.
New organizations start with an advisory merge gate. An admin can enable blocking after adding postil/gate to the repository rules. Postil keeps the check neutral while the merge gate is advisory.
With blocking enabled, invalid model output and incomplete hosted reviews fail closed. Turning blocking off reconciles the latest check for every pull request back to neutral.
Failure semantics
postil/gateon operational error: failure when blocking is enabledpostil/reviewon operational error: neutral, with the error summaryClean PR: both green, zero comments
Advisory merge gate: neutral, with findings left on the review
02 — Permissions
Minimal permissions for reviews, checks, and approvals.
| Permission | Level |
|---|---|
| contents | read |
| pull_requests | write (review comments) |
| checks | write (the two check-runs) |
| issues | write (explicit command replies) |
| members | read (live approval authorization) |
| metadata | read |
| contents: write | never requested |
In a publicly reported August 2025 disclosure, security researchers described a remote-code-execution chain in the category leader's review pipeline that exposed installation credentials carrying write access across a large share of customer repositories. The lesson is structural: a reviewer does not need push access, so Postil's GitHub App never asks for it.
Installation tokens are minted on demand from the App key, held in memory only, and expire within an hour. The App private key lives in your environment (or ours, hosted) and is never written to the database or logs.
03 — Data
Only the review envelope is retained.
The control plane persists exactly one artifact per review: the envelope, a JSON document with the summary, findings (path, line, severity, confidence), token usage, and gate verdict. Your diff is fetched at review time, sent through either Postil's configured provider path or your BYOK provider path, and discarded with the process. CLI and self-hosted reviews send diffs directly to the endpoint you configure.
Bring-your-own API keys are sealed with AES-256-GCM before they touch the database, and the settings form is write-only: a stored key can be replaced or removed, never read back out.
Stored per review
{
"summary": "Refund path missing idempotency key.",
"silent": false,
"findings": [
{
"path": "src/billing/invoice.ts",
"line": 84,
"severity": "error",
"kind": "risk",
"confidence": 0.91,
"title": "Non-idempotent refund",
"body": "Retried webhook double-credits."
}
],
"counts": { "info": 0, "warn": 0, "error": 1, "suppressed": 5 },
"gate": { "failOn": "error", "failing": true },
"usage": { "promptTokens": 8421, "completionTokens": 612 }
}Illustrative envelope.
The full schema is documented at /docs/envelope.
04 — Methodology
How the silence rate is computed.
The number on the homepage comes from replaying the released CLI against a fixed sample of 126 recently merged public pull requests (around 18 repos across JS/TS, Python, Go, and Rust) and reading the envelope each review produced. Nothing is hand-labeled after the fact.
A PR counts as silent when its envelope has zero findings and the run posted zero PR comments (the same condition postil/review uses to decide whether to comment at all). The silence rate is silent ÷ 126.
The chart to the right (the same one shown on the homepage) only plots the non-silent runs: each shipped finding's confidence is sorted into the envelope's confidenceBuckets (five buckets of width 0.2, 0.0–1.0), then summed across all 126 envelopes. It is a count of findings, not PRs: one PR can contribute more than one point.
y: findings shipped · x: finding confidence
finding confidence
57 shipped findings across 126 recently merged public pull requests, June 2026 — 23 at 0.6–0.8 confidence, 34 at 0.8–1.0. None below 0.6.
Per-PR envelope fields used
- →
findings.length === 0and 0 comments posted → counts toward the silent numerator - →
confidenceBucketsfrom every non-silent envelope, summed → the point values - →n = 126 is the full sample: every PR in the run contributes to the denominator
Raw envelopes and the run log are kept privately; the aggregate figures on this site are not hand-tuned between runs. See Why Postil for how this compares to what incumbents publish.
Four pipeline steps produce one stored envelope.