Tencent/WeKnora · error

delete invalid MCP OAuth token: %w

Error message

delete invalid MCP OAuth token: %w

What it means

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.

Source

Thrown at internal/mcp/oauth_lifecycle.go:243

	permanent, resetClient := permanentRefreshFailure(refreshErr)
	if permanent {
		return r.invalidateToken(ctx, resetClient, "the refresh token or OAuth client is no longer valid")
	}
	return &OAuthRefreshTemporaryError{Err: refreshErr}
}

func oauthTokenMaterialChanged(current, observed *types.MCPOAuthToken) bool {
	if current == nil || observed == nil {
		return current != observed
	}
	return current.AccessToken != observed.AccessToken ||
		current.RefreshToken != observed.RefreshToken ||
		!current.ExpiresAt.Equal(observed.ExpiresAt)
}

func (r *oauthRuntime) invalidateToken(ctx context.Context, resetClient bool, reason string) error {
	if err := r.repo.DeleteTokenForPrincipal(ctx, r.tenantID, r.principal, r.serviceID); err != nil {
		return fmt.Errorf("delete invalid MCP OAuth token: %w", err)
	}
	if resetClient {
		if err := r.repo.DeleteClient(ctx, r.tenantID, r.serviceID); err != nil {
			return fmt.Errorf("delete invalid MCP OAuth client registration: %w", err)
		}
	}
	return &OAuthReauthorizationRequiredError{Reason: reason}
}

func permanentRefreshFailure(err error) (permanent bool, resetClient bool) {
	var oauthErr transport.OAuthError
	if errors.As(err, &oauthErr) {
		switch strings.ToLower(oauthErr.ErrorCode) {
		case "invalid_grant", "invalid_token", "bad_refresh_token", "expired_token":
			return true, false
		case "invalid_client", "unauthorized_client":
			return true, true
		}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Check DB write permissions: the repo user needs DELETE on the token table
  2. Fix database connectivity / read-only state and retry the operation
  3. Manually delete the stale token row so the user is prompted to re-authorize
  4. Check the wrapped cause for context cancellation and increase the deadline

Example fix

// before
err := rt.InvalidateToken(ctx, tenantID, principal, serviceID)
// after: log-and-fallback if delete fails, since reauth is the goal
if err != nil && isReadOnlyDB(err) {
    log.Warnf("could not delete invalid token; forcing reauth flag instead: %v", err)
    return repo.MarkTokenNeedsReauth(ctx, tenantID, principal, serviceID)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure DB is writable before triggering refresh flows
if err := db.PingContext(ctx); err != nil { return err }
if readOnly, _ := dbIsReadOnly(ctx); readOnly { return fmt.Errorf("db read-only") }

Try / catch

if err := rt.Refresh(ctx, tenantID, principal, serviceID); err != nil {
    var reauth *mcp.OAuthReauthorizationRequiredError
    if errors.As(err, &reauth) {
        // invalidation delete may have failed; check and clean up manually
        if delErr := repo.DeleteTokenForPrincipal(ctx, tenantID, principal, serviceID); delErr != nil {
            log.Errorf("token delete failed, manual cleanup needed: %v", delErr)
        }
        return redirectToReauth(reauth)
    }
    return err
}

Prevention

When it happens

Trigger: 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).

Common situations: Database in read-only mode; DB user lacks DELETE privilege on the token table; connection dropped mid-write; disk full on the database host.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/0c32389988be1e50. Report an issue: GitHub.