-
Notifications
You must be signed in to change notification settings - Fork 12
Description
Summary
Replace the manual PAT entry in town settings with a one-click GitHub OAuth Device Flow that authenticates git operations as the individual user. Supersedes #1001 (git commit co-authorship) — this approach solves attribution, contribution credit, and PR authorship in one step, without needing to separately resolve user name/email or inject commit trailers.
Parent: #204 (Phase 4: Hardening)
Supersedes: #1001
Problem
Today, agent git operations (push, PR creation, merge) either use a GitHub App installation token (actions show as "bot") or a manually-entered PAT (requires the user to navigate to GitHub settings, create a token, copy-paste it). Neither is ideal:
- App tokens: Commits and PRs are attributed to the app, not the user. No contribution graph credit. No human provenance trail.
- Manual PAT: High-friction setup. Users frequently get scopes wrong, tokens expire, and there's no refresh mechanism.
Solution
Add a GitHub OAuth Device Flow to the town settings UI. The user clicks "Connect GitHub," enters a short code in GitHub's browser UI, and the resulting OAuth user token is stored in the town config. All subsequent git operations authenticate as that user.
Why Device Flow (not standard OAuth redirect)
The standard OAuth Authorization Code flow requires a redirect URI, which is awkward for a settings page that's already open. The Device Flow is designed for exactly this UX: the user authorizes in a separate browser tab and the token arrives via polling. No redirect, no popup, no copy-paste.
What the user token provides
| Capability | App Installation Token | User OAuth Token |
|---|---|---|
Commit GIT_AUTHOR |
Bot / app identity | User's name + email |
| PR author on GitHub | App | User |
| Contribution graph credit | No | Yes |
| Access scope | Repos where app is installed | All repos user can access |
gh CLI compatible |
Yes | Yes |
| Token lifetime | 1 hour (auto-refresh) | Non-expiring (until revoked) |
With the user token, GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL and GIT_COMMITTER_NAME/GIT_COMMITTER_EMAIL can both be resolved from the token's identity — or the agent can remain as committer while the user is the author (per #1001's original design). Either way, the token itself handles authentication.
UX Flow
- User opens Town Settings → Git Authentication section
- Clicks "Connect GitHub" button
- UI calls worker endpoint
POST /api/towns/:townId/auth/github/device-code - Worker calls
POST https://github.com/login/device/codewith the OAuth App'sclient_idand scopes (repo,workflow) - GitHub returns
device_code,user_code,verification_uri,interval - UI displays: "Enter code ABCD-1234 at github.com/login/device" with a link that opens in a new tab
- Worker starts polling
POST https://github.com/login/oauth/access_tokenwith thedevice_codeeveryintervalseconds - User enters the code in GitHub's UI and clicks "Authorize"
- Poll returns
access_token+token_type+scope - Worker stores the token in
townConfig.git_auth.github_tokenand resolves the user's name/email viaGET /useron the GitHub API - UI shows "Connected as @username" with a disconnect button
Data stored
townConfig.git_auth = {
github_token: string; // OAuth user token
github_username: string; // Resolved from GET /user
github_user_email: string; // Resolved from GET /user/emails (primary)
github_user_name: string; // Resolved from GET /user (display name)
connected_at: string; // ISO timestamp
};Agent dispatch changes
When dispatching any agent, pass the user's git identity to the container:
GIT_AUTHOR_NAME: townConfig.git_auth.github_user_name ?? agentName,
GIT_AUTHOR_EMAIL: townConfig.git_auth.github_user_email ?? `${agentName}@gastown.kilo.ai`,
GIT_COMMITTER_NAME: `${agentName} (${agentRole})`,
GIT_COMMITTER_EMAIL: `${agentName}@gastown.kilo.ai`,
GH_TOKEN: townConfig.git_auth.github_token,This gives:
- Author = the human (contribution credit, provenance)
- Committer = the agent (traceability)
ghCLI = authenticated as the user (PRs opened by the user)
Commit trailers (from #1001)
Retain the polecat system prompt instruction to include trailers:
Co-authored-by: Toast (Polecat) <toast@gastown.kilo.ai>
Bead-ID: gt-abc-123
The Requested-by trailer from #1001 becomes unnecessary since GIT_AUTHOR already identifies the human.
Implementation
New endpoints
POST /api/towns/:townId/auth/github/device-code— Initiates the device flow, returns{ userCode, verificationUri, expiresIn }GET /api/towns/:townId/auth/github/poll— Polls for the token (called by UI on interval). Returns{ status: 'pending' | 'complete', username? }DELETE /api/towns/:townId/auth/github— Disconnects: clears the stored token
UI changes
- Town Settings → Git Authentication section: replace the PAT text input with a "Connect GitHub" button
- Connected state shows
@usernamewith avatar and a "Disconnect" button - Keep the manual PAT input as a fallback (collapsed/advanced section) for GitHub Enterprise or non-GitHub providers
Prerequisites
- A GitHub OAuth App (or enable Device Flow on the existing GitHub App) with
repoandworkflowscopes - The OAuth App's
client_idstored as a worker secret/env var - Device Flow enabled in the OAuth App settings on GitHub
Acceptance Criteria
- "Connect GitHub" button in town settings initiates Device Flow
- User authorizes in GitHub tab, token is stored automatically
- Connected state shows username with disconnect option
- Agent commits have
GIT_AUTHOR= user,GIT_COMMITTER= agent - PRs created by agents show the user as author on GitHub
- GitHub contribution graphs credit the user
-
ghCLI in the container authenticates as the user - Manual PAT entry still available as fallback
- Token survives town restarts (persisted in townConfig)
- Disconnect clears the token from townConfig
Notes
- For org-scoped towns (feat(gastown): add org-level towns, rigs, and auth middleware #1153), different users may connect different GitHub accounts. The token is per-town, so the town creator's identity is used. Per-rig tokens could be a future extension.
- GitLab has an equivalent Device Flow — this issue focuses on GitHub but the architecture should be extensible.
- Non-expiring OAuth tokens can be revoked by the user at any time on GitHub (Settings → Applications). The agent should handle 401s gracefully and surface "reconnect needed" in the UI.