Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/4-implementation/agent-tools/github.md
iris 97671e28d5 docs: move implementation sections out of the handbook pages
Three handbook pages carried sections an operator never reads, named after
source paths and build internals. They move to pages under 4-implementation,
and each handbook page keeps a short pointer.

- forge.md: the notification poller (what it polls, how it formats,
  de-duplicates and marks notifications read) is now
  agent-tools/forge-notify.md. The forge page keeps a short Notifications
  section.
- matrix.md: the retired hive-side matrix code, the evaluation assertions and
  the fluffychat-web build fixes are now networking/matrix-homeserver.md.
- github.md: the Implementation section (how `gh` and `git push`
  authenticate, and the GitHub poller) is now agent-tools/github.md.

docs/README.md and the agent-tools README list the new pages, and AGENTS.md's
note on mixed operator and implementation docs now says to move the detail
and leave a pointer.
2026-11-09 21:27:43 +01:00

4.5 KiB

GitHub accounts: how the agent authenticates

How the agent's gh and git push authenticate, and how the GitHub notification poller works. What an operator does to give an agent an account is in GitHub accounts.

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/<owner>/<repo> 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 <state>/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 fail unauthenticated. The build bakes the token path into the scripts (rather than reading it from an env var), because claude's Bash tool runs in a minimal environment that wouldn't carry one.

Notifications

hive-github-notify polls github.com for the agent, turning each unread notification thread into a todo. it's a separate binary and a separate systemd unit from the internal forge's poller (hive-forge-notify, see the forge notification poller), installed by nix/agent-modules/github.nix under services.hyperhive.agent.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's also why this isn't 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's Restart = on-failure and never always — a clean exit on a PAT-less agent must not become a restart loop. The poller waits about ten minutes for the PAT to appear before it gives up. A path unit (hive-github-notify.path) starts the poller again when <state>/github-token changes, so a PAT that lands later still enables polling. Once polling, it re-reads the file on every tick, so it picks up a rotated PAT on the next tick without a restart.

Forgejo models its notifications API 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. Lenient deserializers absorb the two real differences — GitHub sends the thread id as a string where Forgejo sends a number, and says PullRequest where Forgejo says Pull. hive-github-notify prefixes todo keys gh:<id> so a github thread id can't collide with a forge one.

Two host differences worth knowing before touching this code:

  • Auth scheme, not just value. Forgejo takes Authorization: token <t>; GitHub wants Bearer <t> plus Accept: application/vnd.github+json, X-GitHub-Api-Version and a User-Agent. Sending Forgejo's form to GitHub doesn't 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 the poller's own tick) and rate-limits callers who ignore it, so the loop re-arms to the server's interval whenever that's slower than the poller's. A hint faster than the poller's own tick isn't 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 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.