Migrating to the context-aware credentials Backend¶
The pkg/credentials.Backend interface and its package-level helpers have been extended to accept a context.Context. This is a breaking change for any code that implements the Backend interface directly or calls credentials.Store / credentials.Retrieve / credentials.Delete / credentials.Probe.
The change unblocks remote-store backends โ Hashicorp Vault, AWS Secrets Manager, custom corporate stores โ that need deadlines and cancellation to guard against hanging calls. The OS-keychain backend in pkg/credentials/keychain accepts the context for interface uniformity but does not honour it (platform APIs don't expose cancellation).
Breaking Changes¶
credentials.Backend interface¶
Package: pkg/credentials
Before:
type Backend interface {
Store(service, account, secret string) error
Retrieve(service, account string) (string, error)
Delete(service, account string) error
Available() bool
}
After:
type Backend interface {
Store(ctx context.Context, service, account, secret string) error
Retrieve(ctx context.Context, service, account string) (string, error)
Delete(ctx context.Context, service, account string) error
Available() bool
}
Migration: Add a leading ctx context.Context parameter to each method on your custom Backend implementation. Backends that perform network I/O should honour the context (pass it through to their HTTP/gRPC client); local-only backends may accept and ignore it.
// Before
func (b *MyBackend) Retrieve(service, account string) (string, error) {
return b.store.Get(service + "/" + account)
}
// After
func (b *MyBackend) Retrieve(ctx context.Context, service, account string) (string, error) {
return b.store.GetWithContext(ctx, service + "/" + account)
}
See docs/how-to/custom-credential-backend.md for a worked example with Hashicorp Vault.
credentials.Store / Retrieve / Delete / Probe¶
Package: pkg/credentials
Before:
err := credentials.Store("mytool", "github.auth", token)
secret, err := credentials.Retrieve("mytool", "github.auth")
err := credentials.Delete("mytool", "github.auth")
if credentials.Probe() { ... }
After:
err := credentials.Store(ctx, "mytool", "github.auth", token)
secret, err := credentials.Retrieve(ctx, "mytool", "github.auth")
err := credentials.Delete(ctx, "mytool", "github.auth")
if credentials.Probe(ctx) { ... }
Migration: Thread a context.Context into each call site.
- Call paths that already have a request context (HTTP handlers, cobra
Runfunctions viacmd.Context(), chat provider initialisers, resolver chains) should pass that context. - Call paths with no ambient context (package
init(), fire-and-forget goroutines, synchronous setup wizards) should construct acontext.WithTimeout(context.Background(), 5 * time.Second)so a misbehaving remote backend cannot stall indefinitely.
Test code can use t.Context() (Go 1.24+).
pkg/chat/credentials.go internal resolver signatures¶
Package: pkg/chat (internal)
The unexported helpers resolveAPIKey / resolveFromConfig / retrieveFromKeychainRef now take a context. The callers (newClaude, newOpenAI, newGemini) thread the provider's constructor context through to them.
Migration: If you have a fork or patched version of pkg/chat, pass the ctx you already receive in each provider's new* function to the resolver call.
Non-breaking additions¶
vcs.ResolveTokenContext¶
Package: pkg/vcs
ResolveToken(cfg, fallbackEnv) is preserved as a compatibility shim that internally uses context.Background(). New code should prefer the context-aware form:
// Before (still works, uses context.Background internally):
token := vcs.ResolveToken(cfg.Sub("github"), "GITHUB_TOKEN")
// Recommended (threads the caller's context):
token := vcs.ResolveTokenContext(ctx, cfg.Sub("github"), "GITHUB_TOKEN")
This is not a deprecation โ both forms remain supported. The context-aware variant is preferred anywhere a caller has a context in scope, so slow remote backends honour the enclosing deadline.
credtest.MemoryBackend context handling¶
pkg/credentials/credtest.MemoryBackend now honours context cancellation in Store / Retrieve / Delete โ a cancelled context returns ctx.Err() before the map access. Existing tests that pass a non-cancelled context (including t.Context()) are unaffected.
Test migration¶
The new signatures mean every call site in your test suite needs an explicit context. The simplest change is a project-wide sed:
sed -i \
-e 's|credentials\.Store(|credentials.Store(t.Context(), |g' \
-e 's|credentials\.Retrieve(|credentials.Retrieve(t.Context(), |g' \
-e 's|credentials\.Delete(|credentials.Delete(t.Context(), |g' \
-e 's|credentials\.Probe()|credentials.Probe(t.Context())|g' \
**/*_test.go
Verify with go vet / golangci-lint afterwards.
Why this is worth a breaking change¶
Both backends shipped with GTB are local:
pkg/credentials/keychaintalks to OS IPC that returns in milliseconds.pkg/credentials/credtest.MemoryBackendis in-process.
So the addition of context wouldn't have helped the shipped code directly. The motivation is making the contract suitable for third-party backends that need network I/O:
- Hashicorp Vault โ HTTP(S) round-trip, often behind auth that can expire.
- AWS Secrets Manager / SSM Parameter Store โ IAM-gated HTTPS.
- Google Secret Manager โ gRPC over authenticated transport.
- 1Password Connect, custom corporate stores โ all network-bound.
Without context plumbing, a misconfigured remote backend can hang the CLI indefinitely; with it, callers enforce their own deadlines and cancellation cleanly propagates. The change is small and worth doing once now, while the Backend interface has only three internal implementers and no known external ones.