Skip to content
AgentDock

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.poll to the project's runner, which fetches right away.
  • While deliveries keep arriving, the runner relaxes its issues and prs polling 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:

PermissionWhy
Metadatarequired by GitHub for every App
Issuesissues events (the queue)
Pull requestspull_request and pull_request_review events
Checkscheck_suite and check_run events
Commit statusesstatus events
Contentspush 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

VariableWhy
APP_ENCRYPTION_KEYThe 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_URLThe public HTTPS base of the API that GitHub can reach. The hook URL becomes <PUBLIC_URL>/hooks/github.
APP_URLThe 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:

Terminal
openssl rand -base64 32

Behind the reference proxy, the API lives under /api on the same origin, so PUBLIC_URL includes that prefix:

apps/api/.env
APP_ENCRYPTION_KEY=<32 random bytes, base64>
PUBLIC_URL=https://agentdock.example.com/api
APP_URL=https://agentdock.example.com

Restart the API after changing either variable.

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.

  1. Open the GitHub page

    Go to Admin → Integrations → GitHub App. The Register the App card is shown while nothing is registered.

  2. Choose the owner

    Leave Organization (optional) empty to create the App under your own account, or enter an organization login to create it there.

  3. 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 App AgentDock <host> and carries the read-only permissions, the events and the hook URL. GitHub shows the permissions before you confirm.

  4. Come back

    GitHub redirects your browser to /admin/integrations/github/callback with a one-time code. The API exchanges it at POST https://api.github.com/app-manifests/<code>/conversions and 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.

  1. Click Install on GitHub on the GitHub App page.
  2. Pick the account that owns your repositories and select the repositories.
  3. 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.

StateMeaningPolling
coveredThe project's repository is in an active, non-suspended installationdepends on health
healthyCovered, 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 sinceevery 10 minutes, plus a poll on each delivery
unhealthyAnything elseevery 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:

ReasonWhat it says
not_registeredNo GitHub App is registered yet.
hook_inactiveThe App's webhook is inactive, so GitHub cannot reach this server.
not_coveredThe App is not installed on this repository.
installation_suspendedThe App's installation is suspended.
signature_failureGitHub deliveries failed signature checks; the webhook secret does not match.
no_recent_deliveryNo 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). Without PUBLIC_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-256 is verified in constant time over the raw body, before it is parsed. A missing or wrong signature is answered 401 with no body and makes every project unhealthy until the next verified delivery.
  • X-GitHub-Delivery ids are remembered for 14 days. A repeat answers 200 duplicate and polls nothing.
  • Bodies are limited to 5 MB (413 beyond). 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.

Next steps