Terminology: the dashboard and docs call this the Forge App — the app Hive installs on your forge (your source control system, e.g., GitHub, GitHub Enterprise, GitLab, or Gitea). On GitHub.com and GitHub Enterprise (GHE) the Forge App is a GitHub App; this page covers creating and installing it. Dashboard controls live under Governor Config → Forge App.
Hive can authenticate with either a personal access token or a GitHub App. Use a GitHub App for production hives because installation tokens are scoped to selected repositories and can author PRs as the app bot when github.app_authored_prs is enabled.
The PAT path is HIVE_GITHUB_TOKEN (or github.token in hive.yaml); the App path is github.app_id/github.key_file as described below. If you set app_id/key_file this section does not apply — the App permission table further down does.
Hive never validates token scopes at startup. A PAT with missing scopes fails at request time with generic GitHub 403 responses (fine-grained PATs typically say Resource not accessible by personal access token). The visible symptoms are agents reporting no actionable work, advisory digests not appearing on the tracking issue, empty fleet-stats widgets, or failed issue/PR writes in agent logs — none of which name the missing scope. If you see unexplained 403s, check the scopes below first.
Classic scopes are coarse, so one scope covers every ACMM tier:
| ACMM tier | Minimum classic scopes | Why |
|---|---|---|
| L1–L2 (advisory) | repo (private repos) or public_repo (public repos only) |
Read issues/PRs/contents; post advisory digest comments to the pinned tracking issue (an issues write, even though the tier is otherwise read-only). |
| L3–L4 (issue filing) | same | Create, label, comment on, and close issues. |
| L5–L6 (PR/merge) | same, plus workflow if agent PRs may touch .github/workflows/ |
Push branches, open/update/merge PRs, read checks/statuses. GitHub rejects workflow-file pushes without workflow. |
Add read:org when Hive should identify org members and contributor roles (recommended for org-owned hives).
Fine-grained PATs are supported — the token is sent as a plain bearer, same as a classic PAT. Grant the token access to every repository the hive works on, with repository permissions mirroring the App table below:
| Permission | L1–L2 (advisory) | L3–L4 (issues) | L5–L6 (PR/merge) |
|---|---|---|---|
| Metadata | Read | Read | Read |
| Contents | Read | Read | Read and write |
| Issues | Read and write | Read and write | Read and write |
| Pull requests | Read | Read | Read and write |
| Checks | Read | Read | Read |
| Actions | Read | Read | Read |
| Commit statuses | Read | Read | Read |
Organization permission: Members: Read where contributor/owner identification is configured. Note that Issues read-and-write is needed even at advisory tiers because digests are posted as issue comments.
In GitHub, open Settings → Developer settings → GitHub Apps → New GitHub App (or the equivalent organization settings page). On GitHub Enterprise, do this on your enterprise host (https://<your-ghe-host>/settings/apps/new), not github.com — the app, its install page (https://<your-ghe-host>/github-apps/<app-slug>), and the source control host Hive is configured for must all be the same host.
Recommended values:
- GitHub App name: any unique operator-owned name.
- Homepage URL: your project or Hive dashboard URL.
- Setup URL:
https://<hive-host>/gh-setup. - Redirect on update: enabled.
- Webhook: inactive unless you separately configure webhook channels; the dashboard setup flow does not require webhooks.
- Device Flow: enabled, so dashboard login can use the app's client ID.
- Visibility: private for an organization-specific app; public only if you intentionally operate one app for many unrelated owners.
Repository permissions used by the dashboard setup UI are:
| Permission | Level | Why |
|---|---|---|
| Metadata | Read-only | Required by GitHub. |
| Contents | Read/write | Clone, branch, and push agent changes. |
| Issues | Read/write | Enumerate, comment on, create, and close issues. |
| Pull requests | Read/write | Create, update, approve/merge, and inspect PRs. |
| Checks | Read-only | Monitor CI status. |
| Actions | Read-only | Inspect workflow runs. |
| Workflows | Read/write | Let trusted-tier agents push branches that modify .github/workflows/. Without it GitHub rejects any such push server-side ("refusing to allow a GitHub App to create or update workflow … without workflows permission") no matter what the token requests. Hive degrades gracefully — trusted-tier token minting retries without this permission and logs a warning — but CI-fix PRs from agents stay impossible until it is granted and re-accepted on each installation (an existing install must approve the new permission under Settings → Integrations → the app → Review request). |
Organization permission:
| Permission | Level | Why |
|---|---|---|
| Members | Read-only | Identify contributors and owners where configured. |
- After creating the app, note the App ID, Client ID, and app slug from the app page/URL.
- Generate a private key and mount it into the hive, for example
./secrets/gh-app-key.pemon the host mounted as/secrets/gh-app-key.pem. - Install the app on the organization/repositories Hive manages.
- Configure Hive:
github:
app_id: <app-id>
installation_id: <installation-id> # optional when /gh-setup can complete it
app_slug: <app-slug>
key_file: /secrets/gh-app-key.pem
oauth_client_id: <client-id>For GitHub Enterprise, also set api_url/base_url or the supported forge value so install URLs and API calls target the same host.
When the app's Setup URL points at https://<hive-host>/gh-setup, GitHub redirects back with setup_action and, for install/update, installation_id.
Hive accepts setup_action=install and setup_action=update, verifies that:
- a real
github.app_idis configured, - a private key file is resolvable,
- the installation ID can mint/verify a GitHub App installation, and
- the installation account matches the configured project org.
If verification succeeds, Hive persists github.installation_id, reinitializes the GitHub client, and redirects the browser to /?ghSetup=ok. setup_action=request records a pending approval redirect but does not configure an installation.
The setup endpoint is intentionally public because GitHub opens it in a browser that may not have a Hive session. The query string is not trusted; verification is done with the app key and GitHub API.
To rotate a private key, generate a new key in GitHub, mount it at the configured key_file, restart Hive, then delete the old key in GitHub after the new one is confirmed working. If an installation was replaced, use /gh-setup again or update github.installation_id and restart/reload the hive.
On a hosted fleet the hub — not the hive owner — is the App-key authority. The hub keeps one PEM per cluster at /data/saas/app-keys/<clusterID>.pem (owner-only file modes, on the same PVC as the hub's other secrets) and reconciles it to every spoke on that cluster over the heartbeat. A claimed hive whose cluster has no stored key is delivered key_delivered=false at claim time and then receives nothing on any beat — it stays Degraded on key-missing forever until an operator uploads a key. The hive owner cannot see, supply, or fix this key; every owner-facing surface deliberately stays silent for the operator-side states (key-missing, key-invalid, no-app-assigned).
The upload endpoint is the ONLY way key material enters the hub. Admin-gated:
PUT /api/saas/admin/cluster-app-keys/{clusterID}
Content-Type: application/json
{"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----", "app_id": 123456}
private_key(required) — the GitHub App's PEM private key. Validated before it is persisted; a non-PEM or unparsable key is rejected with 400 and nothing is stored.app_id(optional) — the App's numeric ID; when supplied it is persisted alongside inclusters.jsonso the cluster carries a complete identity.- An unknown
{clusterID}returns 404.
The response echoes back only the key's fingerprint (never the key), so you can verify the right key landed:
{"cluster_id": "oke-frankfurt-1", "app_id": 123456, "has_key": true, "fingerprint": "sha256:..."}Compare that fingerprint with one computed locally from the PEM you meant to upload. Delivery to the spokes then happens automatically on the next heartbeats — no restart needed.
GET /api/saas/admin/cluster-app-keys
returns [{cluster_id, app_id, has_key, fingerprint}, ...] for every cluster. A cluster with has_key: false will strand the first App-requiring hive claimed onto it — check this before pointing a pool at a new cluster.
When a claimed, online hive reports an operator-side App-credential state, the hub raises a critical fleet alert (type app-creds-undelivered) in the dashboard's "Attention needed" panel. The alert names the hive and its cluster, shows how long it has been stranded, and carries the exact PUT remedy above. One alert per hive; it clears automatically once the key is delivered and the spoke reports healthy. The states it covers:
key-missing— no key ever reached the spoke (usually: the cluster has no stored PEM — upload one).key-invalid— a key is present but GitHub rejects the JWTs it signs (it belongs to a different App — replace it with the correct PEM).no-app-assigned— the hive still carries the placeholderapp_idand was never assigned a real App (assign the cluster's App and upload its key).