Postil

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/gate on operational error: failure when blocking is enabled
  • postil/review on operational error: neutral, with the error summary
  • Clean PR: both green, zero comments
  • Advisory merge gate: neutral, with findings left on the review

02 — Permissions

Minimal permissions for reviews, checks, and approvals.

PermissionLevel
contentsread
pull_requestswrite (review comments)
checkswrite (the two check-runs)
issueswrite (explicit command replies)
membersread (live approval authorization)
metadataread
contents: writenever 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

34170

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 === 0 and 0 comments posted → counts toward the silent numerator
  • confidenceBuckets from 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.