Fleet GitHub Identity
The AI agent fleet (ruche) commits and opens PRs as a GitHub machine user, never the personal account (ADR-0054, ADR-0056):
- Blast-radius containment — a credential leaked off a compromisable box acts only as the bot, never the owner, and touches no org membership.
- Honest review boundary — the bot cannot approve its own PRs as the owner.
- Independent lifecycle — the token expires and rotates on its own schedule; clean provenance on every commit.
GitHub ToS permits one machine account alongside a personal one.
PR flow
Section titled “PR flow”| Repo class | Mechanism | Provisioning |
|---|---|---|
Owned (espadat-studio/*, sripwoud/*) |
push collaborator → branches in origin, PR in-repo; master protected so the bot cannot merge |
auberge github invite |
| External | bot forks → pushes to its fork → cross-repo PR | none (public forks are free) |
Config keys
Section titled “Config keys”| Key | Secret | Holds |
|---|---|---|
github_bot_login |
no | the machine account handle |
github_bot_email |
no | git author address, <ID>+<login>@users.noreply.github.com |
github_bot_repos |
no | space-separated owned allowlist, e.g. espadat-studio/auberge sripwoud/dotfiles |
github_bot_token |
yes | fine-grained PAT, e.g. !pa show fleet/github-pat |
github_bot_email decides what a commit’s author line says, and the noreply form is what links it to the bot’s profile — the plain <login>@users.noreply.github.com form links to nothing on an account created after mid-2017. The address with the numeric ID is on the bot’s email settings page, under Keep my email addresses private.
Provisioning
Section titled “Provisioning”Account signup and token minting sit behind a login only a human holds — those steps are the checklist below. The two scriptable verbs are CLI commands reading the config above.
-
Create the machine account (browser, logged out of the owner): https://github.com/signup. Set
github_bot_loginto the handle; give it its own email. -
Set the allowlist and invite the bot (as the owner —
inviterefuses to run when the activeghaccount is the bot):Terminal window auberge config set github_bot_login <handle>auberge config set github_bot_repos "espadat-studio/auberge sripwoud/dotfiles"auberge github invite -
Accept each invitation as the bot: https://github.com/notifications (or the emailed link).
verifyflags any left pending. -
Mint a fine-grained PAT as the bot: https://github.com/settings/personal-access-tokens/new. Resource owner: the bot. Repository access: All repositories (the real allowlist is enforced by the collaborator invites, not the token scope). Permissions: Contents Read and write, Pull requests Read and write, Metadata Read-only. Expiry: 90 days — never no-expiration on a box assumed compromisable; note the rotation date.
-
Store the token and point config at it:
Terminal window pa add fleet/github-patauberge config set github_bot_token "!pa show fleet/github-pat" -
Verify — proves the token is the bot and reaches every allowlist repo (push confirmed via
.permissions.push, since a public repo answers a bare read for any token):Terminal window auberge github verify # exit 0 = verified, 1 = a findingauberge github verify -o json # { outcome, identity_ok, repos: [{repo, state}] } -
Deploy the identity.
auberge deploy ruche -H rucheresolvesgithub_bot_tokenthrough the config!-ref and hands it toghon the box withgh auth login --with-token, routes git’s GitHub credentials through gh, and sets the author to the bot:Terminal window auberge config set github_bot_email "<ID>+<handle>@users.noreply.github.com"auberge deploy ruche -H rucheThe token lands only in gh’s own
~/.config/gh/hosts.yml(0600) — never in/etc/environment, never on a command line (ADR-0065).
Rotation
Section titled “Rotation”Fine-grained tokens expire — 90 days is the ceiling. To rotate: regenerate on the token page, update the secret your !-ref reads, auberge github verify, then auberge deploy ruche -H ruche.
The re-template is idempotent and offline: the role compares the token gh already holds (gh auth token) against the one config resolves to, and logs in only when they differ. A network check could not tell a rotated token from an unreachable API, and would re-authenticate on every run.