v0.x: setup.Initialiser.Configure gains a context parameter¶
What changed¶
The 2026-07-23 architectural review found (CRITICAL) that the forge setup
wizard ran its entire interactive credential stage, storage-mode forms, the
OAuth device flow, and the keychain write, under one 5-second
credentials.KeychainOpTimeout context, so interactive OAuth login always
timed out and silently fell back to manual token entry (spec
2026-07-23-setup-credential-stage-context-scoping).
The fix threads the command context through the initialiser seam and scopes keychain deadlines to individual operations. Three public signatures changed:
| Before | After |
|---|---|
Initialiser.Configure(p *props.Props, cfg Editor) error |
Configure(ctx context.Context, p *props.Props, cfg Editor) error |
forge.RunGitHubInit(p, cfg) |
forge.RunGitHubInit(ctx, p, cfg) |
forge.RunBitbucketInit(p, cfg, opts...) |
forge.RunBitbucketInit(ctx, p, cfg, opts...) |
setup.Initialise, forge.RunGitHubInitCmd, and forge.RunBitbucketInitCmd
already accepted a context and now forward it instead of dropping it.
Migrating a custom Initialiser¶
Add the context parameter to your Configure implementation. The contract:
- The caller's context carries no deadline: interactive stages run at human pace, and cancellation (Ctrl-C) must abort in-flight work.
- Derive a fresh
context.WithTimeout(ctx, credentials.KeychainOpTimeout)at each keychain/backend call site, never spanning a form or OAuth stage.
func (i *MyInitialiser) Configure(ctx context.Context, p *props.Props, cfg setup.Editor) error {
// forms, prompts ... (ctx unbounded)
storeCtx, cancel := context.WithTimeout(ctx, credentials.KeychainOpTimeout)
defer cancel()
return credentials.Store(storeCtx, toolName, account, secret)
}
Callers that invoked Configure directly should pass the cobra command's
context (cmd.Context()); in tests, t.Context().
Scaffolded initialisers¶
Projects scaffolded with --with-initializer before this fix do not compile
The generator's initialiser template was not updated alongside the interface,
so gtb generate command --with-initializer emitted a Configure without the
context parameter. The resulting init.go fails to build:
*FooInitialiser does not implement setup.Initialiser
have Configure(*props.Props, setup.Editor) error
want Configure(context.Context, *props.Props, setup.Editor) error
This is fixed. Re-run gtb generate command --with-initializer (or
gtb regenerate) to refresh init.go.
The generated Init<Name> stub in main.go now takes the context as its first
parameter and receives it from Configure, matching the PreRun<Name> stub:
If you already implemented an Init<Name> body against the old signature, add
the parameter: regeneration rewrites the generated init.go that calls it, but
main.go is yours and is not overwritten.