Forge-aware setup: migrate in-use GitHub auth + SSH operations into the forge providers¶
- Authors
- Matt Cockayne, Claude Opus 4.8 (AI drafting assistant)
- Date
- 2026-07-22
- Status
-
IMPLEMENTED (2026-07-22). All phases (§5) delivered. The forge contract gained the optional
AuthenticatorandKeyManagercapabilities (go/forgev0.2.0); all four provider adapters implement what their forge supports and were published simultaneously at v0.2.0 (github + gitlab: device-flowAuthenticator+KeyManager; gitea + bitbucket:KeyManageronly — manual login fallback). GTB'spkg/setup/github+pkg/setup/bitbucketwere collapsed into one provider-drivenpkg/setup/forgeinitialiser that type-asserts the capabilities on the configured forge; the device-code prompt renders through a huhPrompter. The legacypkg/vcs/githubwide client was deleted, and its dead PR/repo/contents surface is parked in thego/forgewidening spec for a future wave. Shipped in GTB MR !283.Original re-scope (endorsed by the maintainer) and resolved open questions, for the record: bring only the in-use auth + SSH into forge across all providers; park the dead PR/repo/contents. §8 — Q4: prove the model on GitHub first (the old
pkg/vcs/githubcode was the proven reference), then build the other three adapters and publish all four simultaneously; Q3: collapsesetup/github+setup/bitbucketinto one forge-driven initialiser; Q5: Bitbucket stays on the manual app-password fallback (noAuthenticator); Q1: the two coarse capabilities; Q2: minimalPrompter. - Related
- extraction report (the wave-2 github row this replaces),
VCS/forge extraction plan (the release-provider extraction this builds on),
go/forgewidening spec (companion — the decommissioned PR/repo/contents API, drafted in the go/forge repo)
1. Context & re-scope¶
The wave-2 "restore GitHub's wider client across every provider in lockstep" item was
framed around pkg/vcs/github — a wide GitHub client (PR management, repo creation,
SSH keys, file contents, releases, device login). That client is legacy from a
GitHub-centric predecessor of go-tool-base; it predates the go/forge provider
abstraction.
Investigation changes the shape of the work:
- Releases already moved.
go/forge'sProvidercovers releases (+ checksum and signature verification), all four adapters implement it, and self-update is already forge-aware (pkg/setup/update.gousesforge.Provider/forge.Lookup).pkg/vcs/github's own release methods are therefore dead. - PR management, repo creation, file contents are dead — called nowhere in GTB.
- What is genuinely in use is narrow and lives in the setup flow, not the VCS
client's wide API: interactive authentication (
GHLogin, GitHub's OAuth device flow) and SSH-key upload (UploadKey), driven bypkg/setup/github.
So the right work is not to extract the wide client. It is to bring the two
in-use operations into the go/forge provider abstraction — across all providers,
kept in sync — and make pkg/setup forge-provider-driven instead of hardcoded to
GitHub. This extends wave 2 (a real, multi-module effort) rather than closing it with
a deletion.
2. Current state (investigation findings)¶
| Concern | Where it lives today | Status |
|---|---|---|
| Release fetch/verify | go/forge + 4 adapters; pkg/setup/update.go consumes forge.Provider |
done, forge-aware |
| Token resolution | go/forge ResolveToken/ResolveTokenContext |
done |
| Interactive auth (OAuth device flow) | pkg/vcs/github/login.go (GHLogin) + pkg/setup/github |
GitHub-only, to migrate |
| SSH-key upload | pkg/vcs/github (UploadKey) + pkg/setup/github/ssh.go |
GitHub-only, to migrate |
| PR / repo / file-contents | pkg/vcs/github (CreatePullRequest, CreateRepo, GetFileContents, …) |
dead → widening spec |
pkg/vcs/github release methods |
pkg/vcs/github (ListReleases, DownloadAsset, …) |
dead (forge superseded) |
pkg/setup already has a provider/registry shape (setup/{github,bitbucket,ai,telemetry}
initialisers), but the github initialiser is bound to pkg/vcs/github for auth + SSH.
3. Scope¶
Build only what is in use. Bring interactive auth and SSH-key management
into go/forge as optional capabilities, implement them in each adapter as far as the
provider supports, and make pkg/setup drive them via forge. Decommission the dead
PR/repo/contents (and dead release) methods — captured in the go/forge widening spec
for a future wave, not implemented here.
4. Design¶
4.1 Optional capability interfaces on go/forge¶
forge already uses optional interfaces a provider may implement (ChecksumProvider,
SignatureProvider) — a caller type-asserts for the capability. Extend that pattern:
// Authenticator is an optional capability: a provider that supports an interactive
// login flow yielding a token. Providers that only accept a pre-issued token/app
// password do not implement it (setup falls back to manual entry).
type Authenticator interface {
// Login runs the provider's interactive flow (e.g. OAuth device flow) and
// returns a usable token. It is UI-agnostic: it reports the user-code / prompts
// via the supplied Prompter so the CLI owns presentation.
Login(ctx context.Context, p Prompter) (token string, err error)
}
// KeyManager is an optional capability: a provider that can register an SSH public
// key on the account via its API.
type KeyManager interface {
UploadKey(ctx context.Context, name string, publicKey []byte) error
}
Prompter is a small interface the CLI (GTB) implements so forge stays TUI-free
(no forms/huh dependency in forge — GTB renders the device-code prompt).
4.2 Per-provider variance (the crux)¶
Auth models differ; capability interfaces make the differences explicit rather than forcing a lowest common denominator:
| Provider | Interactive auth | SSH-key API |
|---|---|---|
| GitHub | OAuth device flow (migrate GHLogin) |
yes (migrate UploadKey) |
| GitLab | OAuth/device flow or PAT | yes (user keys API) |
| Gitea | PAT (likely no device flow) | yes |
| Bitbucket | app password (no device flow) | yes (or repo/account keys) |
A provider implements Authenticator only where an interactive flow exists; otherwise
pkg/setup detects the missing capability and drives manual token/app-password entry
(the existing fallback path). KeyManager likewise where an SSH-key API exists.
4.3 pkg/setup becomes forge-driven¶
- The setup credential + SSH stages resolve the configured forge provider via
forge.Lookup, then type-assert forAuthenticator/KeyManagerand drive whichever the provider offers, with the manual fallback otherwise. pkg/setup/githubandpkg/setup/bitbucketcollapse toward a single forge-driven initialiser (provider-parameterised), keeping only genuinely provider-specific UI copy where unavoidable (§8 Q3).pkg/vcs/githubandpkg/vcs(its config adapters) are deleted once auth+SSH live in forge-github and setup no longer imports them.
4.4 Keep forge framework-free¶
The new capabilities must not pull GTB or a TUI into forge (depfootprint-guarded).
Auth/SSH use the provider SDK + Prompter; presentation stays in GTB.
5. Delivery (phased)¶
The proving phase, then the lockstep build (Q4):
- Forge contract — add
Authenticator,KeyManager,Promptertogo/forge(interfaces + docs + conformance harness additions). No behaviour yet. - forge-github — the proving ground. Implement
Authenticator(portGHLogin's device flow) andKeyManager(portUploadKey) into the github adapter, TDD. The oldpkg/vcs/githubis the proven reference implementation, so this validates the seam (capability interfaces +Prompter) against known-good behaviour before it is generalised. Do not publish yet — hold until the model is proven end-to-end through the GTB setup migration (step 4a). - forge-{gitlab,gitea,bitbucket} — once the GitHub seam is proven, implement the
capabilities each supports, in lockstep, documenting which capability each provides
(Gitea/Bitbucket per §4.2; Bitbucket = manual fallback, no
Authenticator). Publish all four adapters simultaneously (matched versions) so a downstream never sees a partial-parity release. - GTB setup migration — (a) prove the seam end-to-end by driving GitHub auth+SSH
through the new forge capability in
pkg/setupwhile forge-github is still local; (b) after the simultaneous provider publish, makepkg/setupfully forge-driven for all providers, repoint offpkg/vcs/github, then deletepkg/vcs/github+pkg/vcs;just cigreen. - Decommission dead paths — remove PR/repo/contents (+ dead release methods); land the go/forge widening spec capturing them for a future wave.
Step 1 is a go/forge minor; step 3 is a single simultaneous lockstep release of
the four adapters (after GitHub proves the model in step 2 + 4a); 4–5 are GTB MRs.
6. Testing (TDD/BDD + integration)¶
- TDD for the capability implementations; BDD (godog) for the setup auth/SSH
behaviour contract (given a provider with/without
Authenticator, the right flow runs). Full Diátaxis docs per the module conventions. - Integration against provider APIs uses
httptestfakes for the OAuth/device and key endpoints by default; where a container image exists (e.g. Gitea), the testcontainers pattern fromgo/config-consul/test/integrationapplies —INT_TEST_INTEGRATION-gated, testcontainers a test-only dep out of the module graph.
7. The go/forge widening spec (companion)¶
The decommissioned wide API (PR management, repo creation, file contents) is not deleted into the void — it moves to a widening spec in the go/forge repo that captures the API surface and reference source, to be implemented across providers in a future wave when a concrete consumer needs it. This spec's decommission step (§5.5) lands that companion spec.
8. Open questions — RESOLVED (2026-07-22)¶
- Q1 — Capability granularity. → Two coarse capabilities (
Authenticator,KeyManager); provider auth variance lives insideAuthenticator. - Q2 —
Promptershape. → MinimalPrompter(surface the device user-code + verification URL; the CLI owns polling UX), drafted in Phase 1 against the GitHub device flow. - Q3 — Collapse setup providers. → Yes, one forge-driven initialiser parameterised by provider; provider-specific strings live in data, not code.
- Q4 — Rollout. → Prove on GitHub first, then lockstep-publish all four. The old
pkg/vcs/githubis the proven reference; forge-github validates the seam (capability interfaces +Prompter) against known-good behaviour, and is held unpublished until proven end-to-end through the GTB setup migration. Then the other three adapters are built and all four publish simultaneously at matched versions — a downstream never sees partial parity. - Q5 — Bitbucket auth. → Manual fallback (no
Authenticator); app-password entry, assetup/bitbucketalready does.
9. Acceptance criteria¶
go/forgedefinesAuthenticator,KeyManager,Prompter(optional capabilities, depfootprint-clean, TUI-free).forge-githubimplements both (device-flow auth + SSH-key upload), migrated frompkg/vcs/github; the other adapters implement what they support, documented per provider; lockstep releases.pkg/setupdrives auth + SSH via the configured forge provider (capability-detected, manual fallback); no import ofpkg/vcs/github.pkg/vcs/githubandpkg/vcsare deleted;just cigreen; migration note added.- Dead PR/repo/contents API decommissioned and captured in the go/forge widening spec.
- Full Diátaxis docs, TDD+BDD, integration per §6; ≥90% coverage on new module code.