siyuan-note/siyuan · error
refresh OAuth credentials
Error message
refresh OAuth credentials: %w
What it means
The stored credential has a refresh token and the code attempted refreshOAuthCredential, but the refresh failed with a non-permanent (retryable) error — e.g. network failure, 5xx, or timeout at the token endpoint. Because the error is not permanent, the library keeps the stored tokens and surfaces the failure instead of wiping credentials.
Solutions
- Retry Authorize after confirming the token endpoint is reachable (curl the token URL)
- Check the wrapped refreshErr for the underlying cause (network vs HTTP status) and fix that
- If the IdP rejected the grant permanently (invalid_grant), clear the stored MCP OAuth credential for this server so a fresh authorization flow runs
- Verify system clock correctness if the underlying error mentions token validity
Defensive patterns
Strategy: retry
Try / catch
if err := h.Authorize(ctx, req, resp); err != nil && strings.Contains(err.Error(), "refresh OAuth credentials:") {
// transient by design; schedule a retry with backoff
time.AfterFunc(backoff, retryAuthorize)
} Prevention
- Keep the token endpoint reachable and monitor IdP health
- Avoid aggressive request rates that trip token-endpoint rate limits
- Keep system clocks synchronized (NTP) to prevent intermittent token validation failures
- Clear stored credentials only when the refresh error is permanent, not transient
When it happens
Trigger: Authorize is called with a challenge error other than insufficient_scope, a valid non-expired registration, and a stored RefreshToken; the token endpoint returns a transient error (connection refused, 500, timeout) during the refresh_token grant.
Common situations: IdP briefly down or rate-limiting the token endpoint; network drop between SiYuan and the IdP; token endpoint TLS cert rotation causing transient failures; clock skew causing intermittent validation errors.
Related errors
- discover OAuth authorization server
- OAuth protected resource metadata not found
- server returned
- validate OAuth issuer
- validate OAuth protected resource
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/7235a5eab220f1ca.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/client/oauth.go:238
if hasCredential && credential.Issuer == asm.Issuer {
credential.TokenEndpoint = asm.TokenEndpoint
credential.RevocationEndpoint = asm.RevocationEndpoint
}
if hasCredential && credential.Issuer == asm.Issuer && credential.RefreshToken != "" &&
challengeError != "insufficient_scope" && !credential.Rejected && !oauthClientRegistrationExpired(credential) {
refreshed, permanent, refreshErr := refreshOAuthCredential(ctx, h.client, credential)
if refreshErr == nil {
if saveErr := putOAuthCredential(refreshed); saveErr != nil {
logging.LogWarnf("mcp oauth: save refreshed credentials failed: %s", saveErr)
}
h.sourceMu.Lock()
h.source = &storedOAuthTokenSource{credential: refreshed, client: h.client}
h.sourceMu.Unlock()
setMCPRuntimeStateForContext(ctx, h.server.ID, "oauth_retrying", 0, "", "")
return nil
}
if !permanent {
return fmt.Errorf("refresh OAuth credentials: %w", refreshErr)
}
credential.AccessToken = ""
credential.RefreshToken = ""
credential.Expiry = time.Time{}
if saveErr := putOAuthCredential(credential); saveErr != nil {
logging.LogWarnf("mcp oauth: clear invalid credentials failed: %s", saveErr)
}
}
if !interactive {
setMCPRuntimeStateForContext(ctx, h.server.ID, "authorization_required", 0, "", "")
return errOAuthAuthorizationRequired
}
if !slices.Contains(asm.CodeChallengeMethodsSupported, "S256") {
return fmt.Errorf("OAuth authorization server does not support PKCE S256")
}
if len(asm.ResponseTypesSupported) > 0 && !slices.Contains(asm.ResponseTypesSupported, "code") {
return fmt.Errorf("OAuth authorization server does not support the authorization code response type")
}View on GitHub (pinned to 9f775e8a12)