docker/cli · error
failed to erase tokens
Error message
failed to erase tokens: %w
What it means
Returned by OAuthManager.Logout when eraseTokensFromStore() fails. During logout the manager first erases the stored access/refresh tokens; if the credential store rejects the erase (Erase/Save), logout aborts with the wrapped error before attempting revoke.
Solutions
- Unlock/repair the credential store (unlock keychain, reinstall helper).
- Ensure ~/.docker/config.json is writable.
- Re-run `docker logout`.
- As a last resort, manually remove the entries from config.json / keyring.
Example fix
# before: logout fails erasing docker logout # -> failed to erase tokens unlock-keychain sudo apt-get install docker-credential-pass docker logout
Defensive patterns
Strategy: try-catch
Validate before calling
// verify the store can erase before relying on logout
if err := store.Erase(refreshTokenKey); err != nil { /* fix store first */ } Try / catch
if strings.Contains(err.Error(), "failed to erase tokens") { repairStore(); retry logout } Prevention
- Keep the credential helper installed between login and logout.
- Unlock keyrings before logout.
- Ensure config.json stays writable.
When it happens
Trigger: The credential helper Erase call or config.json rewrite fails: locked keyring, helper binary missing, permission denied on ~/.docker/config.json.
Common situations: Keyring locked at logout time; helper removed since login; file permissions changed; disk full / read-only.
Related errors
- failed to store tokens
- credentials erased successfully, but there was a failure to…
- failed to get tokens
- failed to decode response
- unexpected response from Hub
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/36ab2b3727a8e9eb.
Report an issue: GitHub.
Appendix: source
Thrown at internal/oauth/manager/manager.go:181
// returned.
func (m *OAuthManager) Logout(ctx context.Context) error {
refreshConfig, err := m.store.Get(refreshTokenKey)
if err != nil {
return err
}
if refreshConfig.Password == "" {
return nil
}
parts := strings.Split(refreshConfig.Password, "..")
if len(parts) != 2 {
// the token wasn't stored by the CLI, so don't revoke it
// or erase it from the store/error
return nil
}
// erase the token from the store first, that way
// if the revoke fails, the user can try to logout again
if err := m.eraseTokensFromStore(); err != nil {
return fmt.Errorf("failed to erase tokens: %w", err)
}
if err := m.api.RevokeToken(ctx, parts[0]); err != nil {
return fmt.Errorf("credentials erased successfully, but there was a failure to revoke the OAuth refresh token with the tenant: %w", err)
}
return nil
}
const (
accessTokenKey = registry.IndexServer + "access-token"
refreshTokenKey = registry.IndexServer + "refresh-token"
)
func (m *OAuthManager) storeTokensInStore(tokens api.TokenResponse, username string) error {
return errors.Join(
m.store.Store(types.AuthConfig{
Username: username,
Password: tokens.AccessToken,
ServerAddress: accessTokenKey,View on GitHub (pinned to 4f84911bfe)