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) — review and answer only; it 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). No external queue service; 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 (advisory) and postil/gate (blocking). The envelope is stored; the diff is not.
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
Fail closed, always.
When the model returns invalid or ungrounded output, Postil does not shrug and pass. The CLI retries one JSON repair, then emits a synthetic error finding and fails the gate. When the hosted worker crashes or a review exceeds its ten-minute deadline, the watchdog completes the gate check-run as failure — not neutral.
A neutral grey check that looks like success is how unreviewed code merges. If Postil cannot vouch for a head commit, the gate says so in red.
Failure semantics
postil/gateon operational error: failure (fail closed)postil/reviewon operational error: neutral, with the error summaryClean PR: both green, zero comments
02 — Permissions
Read your code. Write your checks. Nothing else.
| Permission | Level |
|---|---|
| contents | read |
| pull_requests | write (review comments) |
| checks | write (the two check-runs) |
| 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
We store the review, not the code.
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 by the CLI at review time, sent to the model endpoint you configured, and discarded with the process.
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.
Four steps in, one envelope out.