GitHub App and Orb
A self-host needs webhook delivery and installation tokens. Direct GitHub App is the default, recommended model — Orb broker mode is private/managed-beta only.
Choose a connection mode
- Direct GitHub App (recommended default)
- Your self-host stores its own App id, slug, private key, and webhook secret, and mints installation tokens directly. No shared quota, no dependency on gittensory's own infrastructure to process a review.
- Brokered Orb (private/managed-beta only)
- Your self-host uses ORB_ENROLLMENT_SECRET to request short-lived installation tokens from the central Orb broker instead of holding its own App key. Not open for general public use — see the operational risks below before considering it.
One-click App creation (recommended for a Direct App)
Before the App exists (no GITHUB_APP_ID set yet), the self-host serves a setup wizard at GET /setup. It renders a form that POSTs a GitHub App manifest — the exact permission and event set below, pre-filled — to GitHub's own App-creation flow. GitHub creates the App with the correct configuration in one step and redirects back to exchange credentials automatically; there is no manual permission checklist to get right or wrong. The route is disabled once an App is configured, so it can't rebind a live install.
PUBLIC_API_ORIGIN=https://reviews.example.com # exact public URL, embedded in the manifest
SELFHOST_SETUP_TOKEN=change-this-long-random-value # unlocks /setup for a freshly-booted instanceopen "https://reviews.example.com/setup"bashEnter SELFHOST_SETUP_TOKEN in the browser form. For scripted setup checks, send the token in an x-setup-token header or Authorization: Bearerheader instead; never place the setup token in the URL.
Direct App permissions
- Pull requests: write.
- Checks: write — the gate posts a check-run;
checks: readalone 403s that write (silently fails the first review with no obvious cause). - Issues: write.
- Contents: write — required for BOTH merging and the auto-maintain
update_branchaction.contents: readlooks sufficient at creation time but silently breaks auto-merge later with no error surfaced in the UI; there is no lesser permission that keeps merge/update-branch working. - Commit statuses: read.
- Metadata: read.
- Actions: write — lets a repo opt into cancelling a closed PR's in-flight CI runs (the
contributorCapCancelCisetting). Off by default and never required: a repo that doesn't enable it, or an installation that hasn't re-approved this permission on an existing App, sees no behavior change — the cancellation attempt is skipped and logged, never blocking the close itself.
Events: pull request, pull request review, push, issues, check suite, check run, and status.
Re-approving a permission bump on an existing App
A future release can widen this permission list (most recently, Actions: write for the opt-in CI-cancellation feature). GitHub does not silently grant a new permission to an App that's already installed — the operator who owns the App must explicitly re-approve it, the same one-time consent step as the original install.
Until you re-approve, the self-host keeps working exactly as before: any feature that needs the new permission degrades gracefully (skipped and logged, never a hard failure) rather than erroring. There's no forced upgrade window.
To re-approve:
- Open your App's settings page —
https://github.com/settings/apps/<your-app-slug>/permissions(organization Apps:https://github.com/organizations/<org>/settings/apps/<your-app-slug>/permissions). - GitHub shows a diff between the App's currently-granted permissions and what the App manifest now requests. Review it, then save — GitHub sends the installation owner a request to accept the new grant.
- Accept the request (as the installation owner, on each installed org/account). The new permission takes effect immediately; no App reinstall or webhook resubscription needed.
Direct App env
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=my-gittensory-app
GITHUB_APP_PRIVATE_KEY_FILE=/run/secrets/github-app-private-key.pem
GITHUB_WEBHOOK_SECRET=<same-secret-configured-on-the-app>Telemetry is separate from token brokerage
These are two independent things people conflate because they're both "Orb": anonymized fleet-calibration telemetry export (enabled by default, works in either connection mode) and token brokerage (optional, private/managed-beta only, lets your self-host get installation tokens from gittensory instead of holding its own App key). Choosing Direct App mode does not opt you out of telemetry, and it's what makes the homepage counters and cross-fleet gate calibration reflect direct installs, not just brokered ones.
- What's exported
- Per resolved PR: the gate verdict, the realized outcome (merged/closed), a reversal flag, a bucketed reason category, and cycle time.
- What's never exported
- Repo/owner/PR names, commit SHAs, source code, diffs, comments, or logins. Repo/PR identifiers are HMAC-anonymized by default with a per-instance secret gittensory's own collector never holds.
- Disabling it
- Set ORB_AIR_GAP=true to compute everything locally and send nothing — the only supported opt-out. There is no partial opt-out short of air-gapping.
ORB_ANONYMIZE=true), not unconditionally — an operator can set ORB_ANONYMIZE=false to export raw repo/PR names instead. There's no scenario where gittensory's own hosted collector needs raw names; the toggle exists for an operator running their own collector (see ORB_COLLECTOR_URL below) who wants readable identifiers in their own infrastructure. Leave this at the default unless you control the collector end.ORB_COLLECTOR_URL overrides the export endpoint — default gittensory's hosted collector, or point it at your own private collector if you're aggregating telemetry yourself instead of sending it to gittensory. ORB_COLLECTOR_TOKEN is the bearer credential for that private collector; leave it unset when using gittensory's own hosted collector, which accepts unauthenticated, rate-limited, aggregate-only exports.
Brokered Orb env
ORB_ENROLLMENT_SECRET=<issued-once-by-orb>
ORB_BROKER_URL=https://gittensory-api.aethereal.dev
ORB_RELAY_MODE=pull # or omit for push (the default) -- see "Choosing a relay mode" belowORB_APP_ID overrides the seed used to derive this instance's stable, anonymous instance_id in telemetry exports — normally derived from GITHUB_APP_ID. A brokered instance holds no App ID of its own (it uses the broker's tokens instead), so its identity falls back to the export secret unless you set ORB_APP_ID explicitly. Most operators never need to set this; it exists so a brokered instance's telemetry identity can be pinned independent of any App ID.
Choosing a relay mode: pull vs. push
Brokered mode still needs a way for GitHub webhook events to reach your self-host through the broker. ORB_RELAY_MODE picks how:
- pull (recommended for NAT/tailnet — no public ingress needed)
- The container polls the broker outbound on a short interval and drains queued events -- no inbound endpoint is ever exposed, and PUBLIC_API_ORIGIN is not required. A failed registration attempt is non-fatal (logged as a warning, not an error): the drain loop keeps retrying on its own schedule and events still arrive once it succeeds.
- push (the default — requires a stable public origin)
- The broker calls your self-host directly at PUBLIC_API_ORIGIN, which must be a real, internet-reachable, TLS-terminated URL -- the broker validates it server-side at registration time and rejects a loopback or private address outright. A failed registration is fatal: the container looks healthy but never receives an event, since there's no fallback delivery path.
ORB_RELAY_MODE=pull. It needs no DNS record, TLS certificate, or firewall rule of its own, and tolerates a transient broker outage more gracefully (see the release checklist's known-warnings table below). Use push only once you already have a stable, publicly reachable HTTPS origin for this instance — the Direct App setup wizard, for instance, always requires one anyway, so an operator running Direct App today has it available for brokered push mode too.Minimum broker safeguards before a public rollout
A maintainer go/no-go checklist — do not open brokered enrollment beyond a small, known, controlled cohort until every item below is true:
- Enrollment quota — a hard cap on how many brokered installs can be active at once, not just an informal agreement.
- Per-install concurrency limit — one brokered install cannot occupy an unbounded share of the token-minting or webhook-relay pipeline.
- Per-install rate budget — a ceiling on GitHub API calls attributable to a single enrollment, independent of the other installs sharing the broker.
- Revocation path — an enrollment secret can be revoked immediately, without waiting for a deploy, when it's compromised or the install is abusive.
- Metrics broken out by enrollment — token-mint volume, webhook-relay volume, and error rate are visible per-enrollment, not only aggregated across every brokered install, so one bad actor is identifiable instead of hiding in the average.
See Troubleshooting for what a degraded brokered relay looks like in logs today, and the release checklist's brokered-mode scenario for the smoke tests that exercise both relay modes.
Webhook checks
curl https://reviews.example.com/health
curl https://reviews.example.com/readybashAfter installing the App on a test repo, open a small PR and confirm the webhook delivery appears in GitHub and a job appears in self-host logs. Continue with Operations for log and metric checks.