Skip to content

fix: return 409 on unique constraint violations for virtual keys, teams, customers, and MCP clients - #4539

Merged
akshaydeo merged 1 commit into
devfrom
06-18-fix_return_409_for_conflict_errors
Jun 19, 2026
Merged

fix: return 409 on unique constraint violations for virtual keys, teams, customers, and MCP clients#4539
akshaydeo merged 1 commit into
devfrom
06-18-fix_return_409_for_conflict_errors

Conversation

@impoiler

Copy link
Copy Markdown
Contributor

Summary

Duplicate resource creation (virtual keys, teams, customers, MCP clients) was returning a generic 500 Internal Server Error instead of a meaningful conflict response. This PR ensures that unique constraint violations from the database are surfaced to callers as HTTP 409 Conflict with descriptive error messages.

Changes

  • Virtual key creation and update now return 409 when a unique constraint violation is detected, replacing the previous behavior where update also checked for "already exists" and "duplicate key" substrings in error messages.
  • Team and customer creation return 409 with a human-readable message when a duplicate name is detected.
  • MCP client creation (across all code paths including OAuth completion) returns 409 when a client with the same name already exists.
  • The IsUniqueConstraintError helper is now consistently used across all these handlers rather than ad-hoc string matching.

Type of change

  • Bug fix

Affected areas

  • Transports (HTTP)

How to test

Attempt to create a virtual key, team, customer, or MCP client with a name that already exists in the system. The API should return a 409 Conflict response with a descriptive error message instead of a 500 Internal Server Error.

go test ./...

Breaking changes

  • Yes
  • No

Related issues

Security considerations

None.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@impoiler, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 13 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 71f60cd1-0b37-4352-998d-e01a15bd084b

📥 Commits

Reviewing files that changed from the base of the PR and between 4f10557 and e462f62.

📒 Files selected for processing (5)
  • framework/configstore/rdb.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/provider_keys.go
  • transports/bifrost-http/lib/config.go
📝 Walkthrough

Walkthrough

HTTP error handling in governance, MCP, and provider key code is updated so that database unique-constraint violations now return HTTP 409 Conflict with specific, domain-appropriate messages. Config store library code maps configstore.ErrAlreadyExists to lib.ErrAlreadyExists, which handlers then detect via errors.Is() to return 409 instead of generic 500 fallthrough or string-matched error text.

Changes

Unique Constraint → HTTP 409 Conflict

Layer / File(s) Summary
Config store and provider key library: map constraint errors
framework/configstore/rdb.go, transports/bifrost-http/lib/config.go
CreateMCPClientConfig error message formatting is adjusted to use %q. AddProviderKey and UpdateProviderKey now explicitly detect configstore.ErrAlreadyExists and map it to the public lib.ErrAlreadyExists instead of falling through to a generic "failed to create/update provider key in store" error.
Provider key handlers: 409 with unified message
transports/bifrost-http/handlers/provider_keys.go
createProviderKey and updateProviderKey handlers detect lib.ErrAlreadyExists and return a standardized, user-facing message about API key name uniqueness across providers, replacing prior behavior of echoing the underlying error text.
Governance handlers: 409 on duplicate entity names
transports/bifrost-http/handlers/governance.go
createVirtualKey, updateVirtualKey, createTeam, and createCustomer gain or improve errors.Is(err, configstore.ErrAlreadyExists) branches returning HTTP 409 with entity-specific "already exists" messages. updateVirtualKey replaces prior string-matching on "already exists"/"duplicate key" with explicit error-type detection, while preserving the badRequestError → 400 path.
MCP handlers: 409 on duplicate client name
transports/bifrost-http/handlers/mcp.go
Both code paths in addMCPClient (per-user-headers and non-per-user-headers) and both paths in completeMCPClientOAuth (per-user and standard server) intercept CreateMCPClientConfig unique-constraint errors and return HTTP 409 with "An MCP client with this name already exists" instead of falling through to the generic 500 handler.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • maximhq/bifrost#3574: Both PRs address unique-constraint handling for provider API keys—this PR maps configstore.ErrAlreadyExists to a library-level lib.ErrAlreadyExists and returns a fixed 409 message from handlers, while the related PR modifies configstore.parseGormError to emit the underlying conflict error.

Poem

🐇 A bunny hopped twice with the same fluffy name,
The server once panicked—oh, what a shame!
Now 409 hops back with a knowing wink,
"That key already exists!" (no need to think).
Duplicates detected, no confusion remains,
The warren runs cleanly through constraint-checked lanes! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: returning 409 HTTP status for unique constraint violations across multiple resource types.
Description check ✅ Passed The description covers all major template sections with detailed explanations of changes, affected areas, testing approach, and confirms no breaking changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-18-fix_return_409_for_conflict_errors

Comment @coderabbitai help to get the list of available commands and usage tips.

@impoiler
impoiler marked this pull request as ready for review June 18, 2026 16:51
@impoiler impoiler self-assigned this Jun 18, 2026
@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge. The change is a targeted error-handling fix with no new code paths, no schema changes, and no mutations to core business logic.

All affected store methods use parseGormError with %w, so errors.Is correctly traverses the error chain. The CreateMCPClientConfig change closes the one gap where the application-level duplicate check was not wrapped. Both the pre-check path and the concurrent-insert (TOCTOU) path are now correctly mapped to ErrAlreadyExists before reaching the handlers. Previous issues raised in review threads — raw error exposure in 409 messages, inconsistent check ordering, and the IsUniqueConstraintError/parseGormError mismatch — are all resolved. No regressions were identified across the five changed files.

No files require special attention.

Important Files Changed

Filename Overview
framework/configstore/rdb.go Changes CreateMCPClientConfig to wrap ErrAlreadyExists with %w so errors.Is propagates correctly through handler layers. The DB-level insert at line 2001 also calls parseGormError, so concurrent-insert races are also correctly mapped to ErrAlreadyExists.
transports/bifrost-http/handlers/governance.go Adds errors.Is(err, configstore.ErrAlreadyExists) → 409 checks for createVirtualKey, updateVirtualKey, createTeam, and createCustomer. Removes the ad-hoc strings.Contains duplicate-key matching that was incorrectly returning 400. Check ordering (badReqErr first, then ErrAlreadyExists) is consistent across all four handlers.
transports/bifrost-http/handlers/mcp.go Adds ErrAlreadyExists → 409 checks at all four CreateMCPClientConfig call sites: two in addMCPClient (standard and OAuth paths) and two in completeMCPClientOAuth (per-user OAuth and server-level OAuth create flows).
transports/bifrost-http/handlers/provider_keys.go Sanitizes the 409 message for createProviderKey (was returning the raw wrapped-error string) and adds a 409 branch to updateProviderKey that was previously missing, now surfacing conflicts instead of falling through to 500.
transports/bifrost-http/lib/config.go Adds ErrAlreadyExists translations in AddProviderKey and UpdateProviderKey so configstore.ErrAlreadyExists is remapped to lib.ErrAlreadyExists before returning to the handler layer; this allows the handler's errors.Is(err, lib.ErrAlreadyExists) check to fire correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[HTTP Handler\ncreate/update request] --> B{badRequestError?}
    B -- yes --> C[400 Bad Request]
    B -- no --> D{errors.Is ErrAlreadyExists?}
    D -- yes --> E[409 Conflict\nsanitized message]
    D -- no --> F[500 Internal Server Error]

    subgraph Store Layer
        G[CreateVirtualKey / CreateTeam\nCreateCustomer / CreateProviderKey] --> H[parseGormError]
        H -- unique constraint --> I[fmt.Errorf ... %w ErrAlreadyExists]
        J[CreateMCPClientConfig] --> K{name exists pre-check}
        K -- found --> L[fmt.Errorf ... %w ErrAlreadyExists]
        K -- not found --> M[tx.Create]
        M -- constraint error --> H
    end

    I --> D
    L --> D
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[HTTP Handler\ncreate/update request] --> B{badRequestError?}
    B -- yes --> C[400 Bad Request]
    B -- no --> D{errors.Is ErrAlreadyExists?}
    D -- yes --> E[409 Conflict\nsanitized message]
    D -- no --> F[500 Internal Server Error]

    subgraph Store Layer
        G[CreateVirtualKey / CreateTeam\nCreateCustomer / CreateProviderKey] --> H[parseGormError]
        H -- unique constraint --> I[fmt.Errorf ... %w ErrAlreadyExists]
        J[CreateMCPClientConfig] --> K{name exists pre-check}
        K -- found --> L[fmt.Errorf ... %w ErrAlreadyExists]
        K -- not found --> M[tx.Create]
        M -- constraint error --> H
    end

    I --> D
    L --> D
Loading

Reviews (9): Last reviewed commit: "fix: return 409 for Conflict errors" | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/governance.go Outdated
Comment thread transports/bifrost-http/handlers/governance.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@transports/bifrost-http/handlers/governance.go`:
- Around line 1862-1868: The error checking order in this handler is incorrect
and causes substring-based misclassification. Move the `badRequestError` type
check (using errors.As) before the `IsUniqueConstraintError` check, since
IsUniqueConstraintError uses substring matching and can incorrectly classify
validation errors containing "duplicate entry" text as HTTP 409 instead of the
correct HTTP 400. Reorder the conditions so that badRequestError is unwrapped
and handled first with its SendError call returning HTTP 400, followed by the
IsUniqueConstraintError check for HTTP 409.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d7cda9b-929d-45eb-a92d-864cfa926a8b

📥 Commits

Reviewing files that changed from the base of the PR and between a0fbaa9 and 2d30ab1.

📒 Files selected for processing (2)
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/mcp.go

Comment thread transports/bifrost-http/handlers/governance.go Outdated
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 2d30ab1 to f5459d0 Compare June 18, 2026 16:57
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from a0fbaa9 to 1d7940b Compare June 18, 2026 16:57
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from f5459d0 to 9737aa4 Compare June 18, 2026 17:11
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from 1d7940b to 2a6e01a Compare June 18, 2026 17:11
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 18, 2026
Comment thread transports/bifrost-http/handlers/governance.go Outdated
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from 2a6e01a to 15aea50 Compare June 18, 2026 17:22
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 9737aa4 to a8c2da7 Compare June 18, 2026 17:22
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from 15aea50 to a2cebf4 Compare June 18, 2026 17:25
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch 3 times, most recently from fe22630 to 9612bce Compare June 18, 2026 17:39
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from a2cebf4 to 7fd0013 Compare June 18, 2026 17:39
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 9612bce to 5812813 Compare June 18, 2026 19:47
@coderabbitai
coderabbitai Bot requested a review from akshaydeo June 18, 2026 19:48
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 5812813 to 4f10557 Compare June 18, 2026 19:52
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 4f10557 to 4dd6889 Compare June 19, 2026 06:43
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch 2 times, most recently from 00e721d to a8114ec Compare June 19, 2026 06:46
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 4dd6889 to 62b6764 Compare June 19, 2026 06:46
@impoiler
impoiler force-pushed the 06-18-fix_return_409_for_conflict_errors branch from 62b6764 to e462f62 Compare June 19, 2026 06:51
@impoiler
impoiler force-pushed the 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables branch from a8114ec to 1aef72e Compare June 19, 2026 06:51

akshaydeo commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jun 19, 6:55 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 19, 7:05 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from 06-18-feat_allow_toggling_additional_columns_in_logs_and_mcp_logs_tables to graphite-base/4539 June 19, 2026 07:04
@akshaydeo
akshaydeo changed the base branch from graphite-base/4539 to dev June 19, 2026 07:04
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review June 19, 2026 07:04

The base branch was changed.

@akshaydeo
akshaydeo merged commit 6e3ef5a into dev Jun 19, 2026
11 checks passed
@akshaydeo
akshaydeo deleted the 06-18-fix_return_409_for_conflict_errors branch June 19, 2026 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants