docker/cli · error
failed to store tokens
Error message
failed to store tokens: %w
What it means
Returned by OAuthManager.Login when storeTokensInStore fails. After parsing claims, the manager persists both the access token (under accessTokenKey) and refresh token (refreshTokenKey, as '<refresh>..<access>' split) into the credential store; if either Store call errors, login fails wrapped with %w.
Solutions
- Ensure the credential helper is installed and unlocked (e.g. `pass init <gpg-key>`, unlock keychain).
- Set credsStore/credHelpers correctly in ~/.docker/config.json, or remove them to use the file store.
- Check write permissions and free space on ~/.docker/config.json.
- Re-run `docker login` after fixing the store.
Example fix
# before: pass not initialized -> failed to store tokens
pass init A1B2C3D4
# config.json uses credsStore=pass; ensure helper exists
{
"credsStore": "pass"
}
docker login
# fallback: use file store (remove credsStore) for CI Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the credential store is writable before login
if err := store.Store(types.AuthConfig{Username:"_probe", Password:"", ServerAddress:"_probe"}); err != nil { return err } Try / catch
if isStoreError(err) { // locked keyring / missing helper
configureFileStore(); retry login
} Prevention
- Install and initialize the credential helper before login.
- Validate the store with a probe write in CI setup.
- Keep ~/.docker/config.json writable.
When it happens
Trigger: The credential store backend (e.g. pass/darwin keychain/wincred/secretservice/file) rejects the write — locked keyring, missing helper binary, permission denied on ~/.docker/config.json, disk full.
Common situations: GNOME keyring locked; `pass` not initialized (no gpg key); credential helper binary missing; read-only home; corrupt config.json; CI with no usable secret store.
Related errors
- failed to erase tokens
- failed to get tokens
- failed to decode response
- unexpected response from Hub
- failed waiting for authentication
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/ea70dba3033a0f60.
Report an issue: GitHub.
Appendix: source
Thrown at internal/oauth/manager/manager.go:144
_, _ = fmt.Fprint(w, "\nWaiting for authentication in the browser…\n")
var tokenRes api.TokenResponse
select {
case <-ctx.Done():
return nil, errors.New("login canceled")
case err := <-waitForTokenErrChan:
return nil, fmt.Errorf("failed waiting for authentication: %w", err)
case tokenRes = <-tokenResChan:
}
claims, err := oauth.GetClaims(tokenRes.AccessToken)
if err != nil {
return nil, fmt.Errorf("failed to parse token claims: %w", err)
}
err = m.storeTokensInStore(tokenRes, claims.Domain.Username)
if err != nil {
return nil, fmt.Errorf("failed to store tokens: %w", err)
}
pat, err := m.api.GetAutoPAT(ctx, m.audience, tokenRes)
if err != nil {
return nil, err
}
return &types.AuthConfig{
Username: claims.Domain.Username,
Password: pat,
ServerAddress: registry.IndexServer,
}, nil
}
// Logout fetches the refresh token from the store and revokes it
// with the configured oauth tenant. The stored access and refresh
// tokens are then erased from the store.
// If the refresh token is not found in the store, an error is notView on GitHub (pinned to 4f84911bfe)