{"record":{"id":"0c32389988be1e50","repo":"Tencent/WeKnora","slug":"delete-invalid-mcp-oauth-token-w","errorCode":null,"errorMessage":"delete invalid MCP OAuth token: %w","messagePattern":"delete invalid MCP OAuth token: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/mcp/oauth_lifecycle.go","lineNumber":243,"sourceCode":"\tpermanent, resetClient := permanentRefreshFailure(refreshErr)\n\tif permanent {\n\t\treturn r.invalidateToken(ctx, resetClient, \"the refresh token or OAuth client is no longer valid\")\n\t}\n\treturn &OAuthRefreshTemporaryError{Err: refreshErr}\n}\n\nfunc oauthTokenMaterialChanged(current, observed *types.MCPOAuthToken) bool {\n\tif current == nil || observed == nil {\n\t\treturn current != observed\n\t}\n\treturn current.AccessToken != observed.AccessToken ||\n\t\tcurrent.RefreshToken != observed.RefreshToken ||\n\t\t!current.ExpiresAt.Equal(observed.ExpiresAt)\n}\n\nfunc (r *oauthRuntime) invalidateToken(ctx context.Context, resetClient bool, reason string) error {\n\tif err := r.repo.DeleteTokenForPrincipal(ctx, r.tenantID, r.principal, r.serviceID); err != nil {\n\t\treturn fmt.Errorf(\"delete invalid MCP OAuth token: %w\", err)\n\t}\n\tif resetClient {\n\t\tif err := r.repo.DeleteClient(ctx, r.tenantID, r.serviceID); err != nil {\n\t\t\treturn fmt.Errorf(\"delete invalid MCP OAuth client registration: %w\", err)\n\t\t}\n\t}\n\treturn &OAuthReauthorizationRequiredError{Reason: reason}\n}\n\nfunc permanentRefreshFailure(err error) (permanent bool, resetClient bool) {\n\tvar oauthErr transport.OAuthError\n\tif errors.As(err, &oauthErr) {\n\t\tswitch strings.ToLower(oauthErr.ErrorCode) {\n\t\tcase \"invalid_grant\", \"invalid_token\", \"bad_refresh_token\", \"expired_token\":\n\t\t\treturn true, false\n\t\tcase \"invalid_client\", \"unauthorized_client\":\n\t\t\treturn true, true\n\t\t}","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/mcp/oauth_lifecycle.go#L225-L261","documentation":"Wraps a repository error from DeleteTokenForPrincipal when the runtime decides the stored OAuth token is invalid and must be removed. The deletion is the first step of invalidation so the next access triggers reauthorization; if the delete fails, the token remains and the error propagates to refreshAsLeaseOwner.","triggerScenarios":"invalidateToken is invoked after permanentRefreshFailure classifies the refresh error as permanent, and repo.DeleteTokenForPrincipal returns an error (DB down, write permission denied, read-only replica, context cancelled).","commonSituations":"Database in read-only mode; DB user lacks DELETE privilege on the token table; connection dropped mid-write; disk full on the database host.","solutions":["Check DB write permissions: the repo user needs DELETE on the token table","Fix database connectivity / read-only state and retry the operation","Manually delete the stale token row so the user is prompted to re-authorize","Check the wrapped cause for context cancellation and increase the deadline"],"exampleFix":"// before\nerr := rt.InvalidateToken(ctx, tenantID, principal, serviceID)\n// after: log-and-fallback if delete fails, since reauth is the goal\nif err != nil && isReadOnlyDB(err) {\n    log.Warnf(\"could not delete invalid token; forcing reauth flag instead: %v\", err)\n    return repo.MarkTokenNeedsReauth(ctx, tenantID, principal, serviceID)\n}","handlingStrategy":"try-catch","validationCode":"// ensure DB is writable before triggering refresh flows\nif err := db.PingContext(ctx); err != nil { return err }\nif readOnly, _ := dbIsReadOnly(ctx); readOnly { return fmt.Errorf(\"db read-only\") }","typeGuard":null,"tryCatchPattern":"if err := rt.Refresh(ctx, tenantID, principal, serviceID); err != nil {\n    var reauth *mcp.OAuthReauthorizationRequiredError\n    if errors.As(err, &reauth) {\n        // invalidation delete may have failed; check and clean up manually\n        if delErr := repo.DeleteTokenForPrincipal(ctx, tenantID, principal, serviceID); delErr != nil {\n            log.Errorf(\"token delete failed, manual cleanup needed: %v\", delErr)\n        }\n        return redirectToReauth(reauth)\n    }\n    return err\n}","preventionTips":["Grant the app DB user DELETE privileges on token tables","Monitor for read-only replica routing on write paths","Alert on any 'delete invalid MCP OAuth token' occurrences","Periodically reconcile orphaned invalid tokens"],"tags":["oauth","database","persistence","token-invalidation"],"backgroundTag":"oauth-token-refresh-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}