Integrations
GitHub App
An optional read-only App that replaces polling with push events.
The GitHub App is optional. Without it, every runner polls its projects' issues and pull requests with gh every 60 seconds. With it, GitHub pushes each change to AgentDock, and the runner polls the moment something happens.
AgentDock registers one App per instance. It only reads: it never comments, labels, merges or writes anything to GitHub through the App.
Why use it
Polling has two costs. A label added on GitHub can take up to a minute to show up in the queue, and every project makes one gh call per minute forever. The App fixes both:
- A delivery from GitHub makes the API send
collector.pollto the project's runner, which fetches right away. - While deliveries keep arriving, the runner relaxes its
issuesandprspolling to every 10 minutes.
The runner's collectors still do the fetching with its own gh, exactly as before. The App only says "look now". Bursts are debounced, so twenty check_run deliveries in a few seconds produce one poll.
Permissions and events
Every permission is Read-only:
| Permission | Why |
|---|---|
| Metadata | required by GitHub for every App |
| Issues | issues events (the queue) |
| Pull requests | pull_request and pull_request_review events |
| Checks | check_suite and check_run events |
| Commit statuses | status events |
| Contents | push events (only pushes to the project's base branch trigger a poll) |
Subscribed events: issues, pull_request, pull_request_review, check_suite, check_run, status, push. GitHub also sends installation, installation_repositories and ping without a subscription.
On the server side, the API uses the App's credentials for four reads only: GET /app, GET /app/installations, GET /installation/repositories and GET /app/hook/deliveries. Issue and pull request content never comes through the App.
Prerequisites
| Variable | Why |
|---|---|
APP_ENCRYPTION_KEY | The App's private key, webhook secret and client secret are stored with AES-256-GCM under this key. Without it, registration is refused with 409 encryption_key_missing and the page shows No APP_ENCRYPTION_KEY on the server. |
PUBLIC_URL | The public HTTPS base of the API that GitHub can reach. The hook URL becomes <PUBLIC_URL>/hooks/github. |
APP_URL | The browser-facing web URL. GitHub redirects back to <APP_URL>/admin/integrations/github/callback. Falls back to WEB_URL. |
Generate the key once and keep it out of database backups:
openssl rand -base64 32Behind the reference proxy, the API lives under /api on the same origin, so PUBLIC_URL includes that prefix:
APP_ENCRYPTION_KEY=<32 random bytes, base64>
PUBLIC_URL=https://agentdock.example.com/api
APP_URL=https://agentdock.example.comRestart the API after changing either variable.
The hook URL is written into the App when it is created. An App registered without PUBLIC_URL gets an inactive webhook, and the page shows Webhook inactive. To fix it, set PUBLIC_URL, restart the API, then Remove the App in AgentDock and register it again.
Only POST /hooks/github has to be reachable from the internet. The rest of the API can stay private. See Reverse proxy and tunnels for Caddy, nginx, Tailscale Funnel and Cloudflare Tunnel options.
Register the App
Registration uses GitHub's manifest flow. Only your browser has to reach AgentDock during it, so it works on a home server behind NAT.
Open the GitHub page
Go to Admin → Integrations → GitHub App. The Register the App card is shown while nothing is registered.
Choose the owner
Leave Organization (optional) empty to create the App under your own account, or enter an organization login to create it there.
Register on GitHub
Click Register on GitHub. Your browser posts a manifest to
https://github.com/settings/apps/new(or the organization variant). The manifest names the AppAgentDock <host>and carries the read-only permissions, the events and the hook URL. GitHub shows the permissions before you confirm.Come back
GitHub redirects your browser to
/admin/integrations/github/callbackwith a one-timecode. The API exchanges it atPOST https://api.github.com/app-manifests/<code>/conversionsand stores the app id, slug, private key, webhook secret and client secret. The secrets are encrypted and never shown again. The client secret is stored but unused, because AgentDock has no GitHub login.
The registration link is valid for one hour and is bound to the admin who started it. If the API restarts in between, start again.
Install it on repositories
A registered App does nothing until it is installed on the repositories your projects use.
- Click Install on GitHub on the GitHub App page.
- Pick the account that owns your repositories and select the repositories.
- Back in AgentDock, click Resync. The Installations table lists each installation, its repositories and the project each maps to.
Repositories are matched to projects by owner/repo, ignoring case. One repository connected on two runners maps to both projects, and each gets its own poll.
Health states
Each project shows its App status in Project → Settings, and the runner receives it in its watch list.
| State | Meaning | Polling |
|---|---|---|
| covered | The project's repository is in an active, non-suspended installation | depends on health |
healthy | Covered, a verified delivery arrived for the installation in the last 60 minutes (or a resync confirmed GitHub's latest delivery succeeded), and no signature failure since | every 10 minutes, plus a poll on each delivery |
unhealthy | Anything else | every 60 seconds |
Health is recomputed every 5 minutes. When it flips to unhealthy, the runner returns to 60-second polling on its next tick, with no restart.
An unhealthy project carries one reason:
| Reason | What it says |
|---|---|
not_registered | No GitHub App is registered yet. |
hook_inactive | The App's webhook is inactive, so GitHub cannot reach this server. |
not_covered | The App is not installed on this repository. |
installation_suspended | The App's installation is suspended. |
signature_failure | GitHub deliveries failed signature checks; the webhook secret does not match. |
no_recent_delivery | No delivery from GitHub for an hour; it may not be able to reach this server. |
The admin page also shows Hook reachable, Last delivery and a Signature failures count. GitHub cannot deliver to this server appears when the last resync found failed deliveries; GitHub's Recent deliveries list for the App shows why each one failed.
Manual entry
If you created the App by hand on GitHub, use Enter credentials by hand instead of the manifest flow. The card lists what the App needs:
- Webhook: tick Active and set the URL to
<PUBLIC_URL>/hooks/github(the card shows the exact URL). WithoutPUBLIC_URL, untick Active. - Webhook secret: a long random string, for example from
openssl rand -hex 32. - Repository permissions, all Read-only, and the events listed above.
Then fill in App ID (top of the App's General page), Slug (the last part of github.com/apps/<slug>), Private key (PEM) (the whole .pem file, BEGIN and END lines included) and Webhook secret. AgentDock checks them with GET /app; a key GitHub refuses is rejected with invalid_credentials.
How deliveries are checked
X-Hub-Signature-256is verified in constant time over the raw body, before it is parsed. A missing or wrong signature is answered401with no body and makes every projectunhealthyuntil the next verified delivery.X-GitHub-Deliveryids are remembered for 14 days. A repeat answers200 duplicateand polls nothing.- Bodies are limited to 5 MB (
413beyond). Compressed bodies are refused. - Payload bodies are not stored. Only the delivery id, event, action, repository and timing are kept, for 14 days.
A leaked webhook secret lets an attacker trigger read-only, debounced polls and nothing else. Rotate it by setting a new secret on GitHub, then removing the App in AgentDock and entering its credentials by hand.
Without the App
Nothing breaks. If GitHub cannot reach your server, or you never register an App, every project stays on 60-second polling and the queue, fleet and approvals pages work the same, just with up to a minute of delay.
Removing the App in AgentDock deletes the registration and installations from AgentDock only. The App itself stays on GitHub; delete it there if you no longer need it. Register, credential changes, resync and removal are each audited.