# GitHub accounts Give an agent a managed GitHub identity — a `gh` CLI and `git push` over HTTPS, both authenticated by an operator-supplied personal access token (PAT) — so it can run GitHub API calls and push commits without any manual `gh auth login`. Provisioning is UI-driven, mirroring the dashboard side of the [matrix account](matrix.md) flow: paste a PAT into the agent's credentials tab and it works. No per-agent nix declaration, no rebuild — the token is injected into the agent's state dir out of band. ## Enabling The integration is **on by default** for every agent (`hyperhive.github.enable = true`), inert until a PAT is provisioned. There is nothing per-agent to declare — an agent gains GitHub simply by having a PAT written to its token file. To turn it off for the whole hive, set the host option: ```nix services.hyperhive.github.enable = false; ``` hive-c0re's meta-flake renderer then injects `hyperhive.github.enable = false` into every agent, so no agent ships the `gh` wrapper or credential helper. (`hyperhive.github.enable` also exists per-agent for completeness, but the hive-wide host switch is the intended control.) github.com only. The token **value** never touches nix — it is written to `/github-token` separately (see [Provisioning](#provisioning)). ## How the agent uses it When enabled, the container gets: - **A `gh` wrapper** on `PATH` (shadowing the raw `gh`) that exports `GH_TOKEN` from the token file at invocation, then execs real `gh`. So `gh pr create`, `gh api …`, etc. just work — `gh` derives the identity from the token. - **A git credential helper** (`git-credential-hive-github`), wired via a host-scoped `/etc/gitconfig` entry for `https://github.com`, so `git push https://github.com//` authenticates as `x-access-token` + the PAT (GitHub ignores the username for PAT auth). Host-scoped, so it never touches the forge (`localhost:3000`) or any other remote. Both scripts read the token from `/github-token` **at invocation time**, so a PAT written (or rotated) mid-session takes effect immediately — no container rebuild or restart. Until the file exists, `gh` / `git push` simply fail unauthenticated. The token path is baked into the scripts at build time (not read from an env var), because claude's Bash tool runs in a minimal environment that wouldn't carry one. ## Provisioning The PAT is operator-supplied. The primary path is the **dashboard credentials tab** (github sub-tab): paste the PAT for an agent and submit (`POST /api/github-account`). There is also a CLI path for recovery/scripting: ```sh hivectl github set-token --token-stdin # paste the PAT on stdin (preferred) hivectl github set-token --token # inline (visible in shell history) ``` Either path has hive-c0re delegate the write to hive-priv, which stores the file `0600` owned by the agent (so the container can read it) — the same credential-injection path as forge/matrix tokens. See [hivectl → GitHub](tools/hivectl.md#github). ## Notifications `hive-github-notify` polls github.com for the agent, turning each unread notification thread into a todo. It is a **separate binary and a separate systemd unit** from the internal forge's poller (`hive-forge-notify`, see [forge.md](forge.md#notification-poller-hive-forge-notifysrcnotifyrs)), installed by `nix/agent-modules/github.nix` under `hyperhive.github.enable`. Both binaries ship from the one `hive-forge-notify` derivation, so the unit is a second `ExecStart` path, not a new package. Two units rather than one daemon with two loops, because it puts the decision in nix: a hive built without this module has **no github poller in its closure at all**, which is what makes GitHub access separable rather than merely switched off. It is also why this is not a cargo feature — a feature would unify across the workspace and cost every crate its build cache. At runtime the poller needs the PAT above. No PAT, no polling: the unit logs why and exits 0, which is why it is `Restart = on-failure` and never `always` — a clean exit on a PAT-less agent must not become a restart loop. Forgejo's notifications API is modelled on GitHub's, so one tolerant parse serves both: `id`, `repository.full_name`, `subject {title,url,latest_comment_url}` and `updated_at` line up field for field. The two real differences are absorbed by lenient deserializers — GitHub sends the thread id as a *string* where Forgejo sends a number, and says `PullRequest` where Forgejo says `Pull`. Todo keys are prefixed `gh:` so a github thread id cannot collide with a forge one. Two host differences worth knowing before touching this code: - **Auth scheme, not just value.** Forgejo takes `Authorization: token `; GitHub wants `Bearer ` plus `Accept: application/vnd.github+json`, `X-GitHub-Api-Version` and a `User-Agent`. Sending Forgejo's form to GitHub does not error — it authenticates as *nobody* and silently drops to the unauthenticated rate limit. The cheap way to tell the two apart is the rate-limit header: `x-ratelimit-remaining` near 5000 is an authenticated user, near 60 is anonymous. - **GitHub sets the cadence.** It returns `X-Poll-Interval` (60s in practice, slower than our own tick) and rate-limits callers who ignore it, so the loop re-arms to the server's interval whenever that is *slower* than ours. A hint faster than our own tick is not a reason to poll harder. ⚠️ **This needs the `notifications` scope on the PAT.** A token minted for `gh` + `git push` typically carries `repo` only, which is enough to push and open PRs but **not** to read the notification stream (nor to mark a thread read, which is the same scope). A PAT without it doesn't break anything: the poller logs the refusal and stays quiet, and the agent simply never gets GitHub wakes. If an agent's GitHub notifications never arrive, check the token's scopes first — the symptom is silence, not an error. ## Security - Use a **dedicated bot account**, never a human's. - Mint a **minimally-scoped PAT** — only the repos/scopes the agent's workflow needs. Agents have passwordless sudo, so a compromised or hallucinating agent can act as the account within the token's scopes; scope is the real blast-radius limiter, and the container boundary is the enforcement. See [security.md](security.md).