siyuan-note/siyuan · warning
OAuth flow is missing or expired
Error message
OAuth flow is missing or expired
What it means
Returned at oauth.go:589-592 by CompleteMCPOAuth when the flowID lookup in oauthFlows.items returns nil OR the flow's Expires time has passed (5-minute lifetime, oauthAuthorizationTimeout). The flow either was never created, was already consumed and deleted, or expired before the callback arrived.
Source
Thrown at kernel/mcp/client/oauth.go:592
}
return base64.RawURLEncoding.EncodeToString(data), nil
}
func removeOAuthFlow(flowID string, flow *oauthFlow) {
oauthFlows.Lock()
if oauthFlows.items[flowID] == flow {
delete(oauthFlows.items, flowID)
}
oauthFlows.Unlock()
}
func CompleteMCPOAuth(flowID, code, state, callbackError, issuer string) error {
oauthFlows.Lock()
flow := oauthFlows.items[flowID]
if flow == nil || time.Now().After(flow.Expires) {
delete(oauthFlows.items, flowID)
oauthFlows.Unlock()
return fmt.Errorf("OAuth flow is missing or expired")
}
if state != flow.State {
oauthFlows.Unlock()
return fmt.Errorf("OAuth state mismatch")
}
if issuer != "" && issuer != flow.Issuer {
oauthFlows.Unlock()
return fmt.Errorf("OAuth issuer mismatch")
}
delete(oauthFlows.items, flowID)
oauthFlows.Unlock()
select {
case flow.Result <- oauthCallbackResult{Code: code, State: state, Error: callbackError}:
return nil
default:
return fmt.Errorf("OAuth callback was already handled")
}
}View on GitHub (pinned to 251596fc0d)
Solutions
- Re-initiate the OAuth authorization and complete the browser consent within the 5-minute window, then let exactly one callback through.
- Avoid duplicate callback tabs; the first callback consumes and deletes the flow.
- If the kernel was restarted, the in-memory flow is gone — start a fresh authorization; flows are intentionally not persisted.
- Confirm the callback flowID in the URL matches the one issued during authorization (no truncation by the AS or browser).
Defensive patterns
Strategy: validation
Try / catch
// CompleteMCPOAuth called by the callback handler — flow may already be gone.
err := CompleteMCPOAuth(flowID, code, state, callbackError, issuer)
if err != nil && strings.Contains(err.Error(), "OAuth flow is missing or expired") {
// Render a user-facing page: 'authorization expired, please retry from SiYuan'.
http.Error(w, "authorization flow expired; restart the authorization", http.StatusGone)
return
} Prevention
- Complete browser consent within the 5-minute flow lifetime.
- Avoid duplicate callback tabs (only the first callback is honored).
- Do not restart the kernel between starting authorization and finishing the callback — flows are in-memory only.
When it happens
Trigger: The OAuth callback HTTP request hits /api/ai/mcp/oauth/callback/<flowID> after Authorize already returned (timeout or ctx cancel which deleted the flow), or after the 5-minute Expires window, or with a flowID that was never registered (process restart wiped the in-memory map).
Common situations: User completes browser consent more than 5 minutes after starting; the original Authorize call already timed out (error 328) and removed the flow; duplicate callback (second tab) arrives after the first consumed the flow; kernel restarted between authorization start and callback.
Related errors
- OAuth authorization timed out
- mcp oauth authorization required
- OAuth authorization failed: %s
- OAuth state mismatch
- OAuth callback did not include an authorization code
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/07debdf0733bdfad.
Report an issue: GitHub.