n8n - ✅(Solved) Fix MCP OAuth re-authorization fails with duplicate key violation on oauth_user_consents [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
n8n-io/n8n#28496Fetched 2026-04-15 06:44:16
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Timeline (top)
referenced ×2commented ×1cross-referenced ×1labeled ×1

Error Message

  1. Error: duplicate key value violates unique constraint "UQ_083721d99ce8db4033e2958ebb4"

Root Cause

The root cause is in packages/cli/src/modules/mcp/mcp-oauth-consent.service.ts handleConsentDecision() calls this.userConsentRepository.insert() without checking whether a consent record already exists for the (userId, clientId) pair. Since oauth_user_consents has a UNIQUE constraint on ["userId", "clientId"], the second INSERT always fails.

Fix Action

Fixed

PR fix notes

PR #28497: fix(core): Use upsert for MCP OAuth user consent to allow re-authorization

Description (problem / solution / changelog)

Summary

Replace insert() with upsert() in McpOAuthConsentService.handleConsentDecision() to fix re-authorization failure for MCP OAuth clients.

When a user who has already consented to an MCP OAuth client goes through the authorization flow again (e.g., after token expiry or reconnecting from an MCP client like Claude.ai), the plain insert() call violates the UNIQUE("userId", "clientId") constraint on oauth_user_consents, throwing:

duplicate key value violates unique constraint "UQ_083721d99ce8db4033e2958ebb4"

The fix uses upsert() with conflict target ['userId', 'clientId'], which updates grantedAt on re-authorization instead of throwing a duplicate key error.

Related Linear tickets, Github issues, and Community forum posts

<!-- Include links to **Linear ticket** or Github issue or Community forum post. Important in order to close *automatically* and provide context to reviewers. https://linear.app/n8n/issue/[TICKET-ID] --> <!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions) <!-- **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** -->
  • Docs updated or follow-up ticket created.
  • Tests included. <!-- A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. -->
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

Changed files

  • packages/cli/src/modules/mcp/__tests__/mcp-oauth-consent.service.test.ts (modified, +43/-8)
  • packages/cli/src/modules/mcp/mcp-oauth-consent.service.ts (modified, +8/-5)
RAW_BUFFERClick to expand / collapse

Bug Description

When a user who has previously authorized an MCP OAuth client attempts to re-authorize (e.g., after token expiry, reconnecting from an MCP client like Claude.ai, or re-triggering the OAuth flow), n8n throws:

duplicate key value violates unique constraint "UQ_083721d99ce8db4033e2958ebb4"

The root cause is in packages/cli/src/modules/mcp/mcp-oauth-consent.service.ts handleConsentDecision() calls this.userConsentRepository.insert() without checking whether a consent record already exists for the (userId, clientId) pair. Since oauth_user_consents has a UNIQUE constraint on ["userId", "clientId"], the second INSERT always fails.

This makes it impossible for any user to re-authorize an MCP client without manually deleting the consent row from the database.

To Reproduce

  1. Set up an MCP OAuth gateway (e.g., Context Forge) with DCR pointed at n8n (/mcp-oauth/register, /mcp-oauth/authorize, /mcp-oauth/token)
  2. As a user, go through the OAuth authorization flow — approve the "wants access to your n8n instance" consent screen → succeeds
  3. Let the access token expire, or manually trigger re-authorization from the MCP client
  4. Go through the OAuth flow again — the consent screen appears, approve it
  5. Error: duplicate key value violates unique constraint "UQ_083721d99ce8db4033e2958ebb4"

Expected behavior

Re-authorization should succeed. When a consent record already exists for the same (userId, clientId), n8n should update the existing record (e.g., refresh grantedAt) instead of attempting a duplicate INSERT.

Debug Info

n8n Version: 2.15.1 Node.js Version: v24.13.1 Database: PostgreSQL (RDS) Execution Mode: main OS: Linux (Kubernetes / EKS, Amazon Linux aarch64)

Operating System

Linux

n8n Version

2.15.1

Node.js Version

v24.13.1

Database

PostgreSQL

Execution mode

main (default)

Hosting

self hosted

extent analysis

TL;DR

Check for an existing consent record before inserting a new one to avoid duplicate key errors during re-authorization.

Guidance

  • Modify the handleConsentDecision() function in mcp-oauth-consent.service.ts to query the database for an existing consent record for the (userId, clientId) pair before calling this.userConsentRepository.insert().
  • If a record exists, update the existing record instead of attempting to insert a new one, for example, by calling this.userConsentRepository.update() with the updated grantedAt timestamp.
  • Verify that the database query correctly checks for the unique constraint on ["userId", "clientId"] to prevent duplicate inserts.
  • Consider adding error handling to catch and handle duplicate key errors, providing a more informative error message to the user.

Example

// Pseudocode example, actual implementation may vary
const existingConsent = await this.userConsentRepository.findOne({
  where: { userId, clientId },
});
if (existingConsent) {
  // Update existing consent record
  await this.userConsentRepository.update(existingConsent.id, { grantedAt: new Date() });
} else {
  // Insert new consent record
  await this.userConsentRepository.insert({ userId, clientId, grantedAt: new Date() });
}

Notes

The provided solution assumes that the userConsentRepository provides methods for finding and updating existing consent records. The actual implementation may vary depending on the repository's API.

Recommendation

Apply a workaround by modifying the handleConsentDecision() function to check for existing consent records before inserting new ones, as this will allow re-authorization to succeed without requiring manual database intervention.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Re-authorization should succeed. When a consent record already exists for the same (userId, clientId), n8n should update the existing record (e.g., refresh grantedAt) instead of attempting a duplicate INSERT.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING