Skip to content

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().