Skip to content

Handle Prisma known request errors without generic Sentry alerts - #1375

Closed
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/out-3676-cursor-automated-triage-response-7ee3
Closed

Handle Prisma known request errors without generic Sentry alerts#1375
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/out-3676-cursor-automated-triage-response-7ee3

Conversation

@cursor

@cursor cursor Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Changes

  • Replace the API error handler's private Prisma runtime instanceof dependency with a structural Prisma known-request error guard so bundled/runtime class mismatches still normalize correctly.
  • Map Prisma P2002 unique constraint violations to a 409 Conflict response and treat them as expected client errors instead of generic logged 400s.
  • Add focused tests for duplicate-resource conflicts and Prisma-like errors crossing module boundaries.

Testing Criteria

  • yarn test src/app/api/tests/utils/withErrorHandler.test.ts --runInBand — 11 tests passed, covering existing API/Zod/Copilot/Prisma normalization plus new structural and P2002 cases.
  • yarn prettier:check — passed.
  • yarn lint:check — passed with 0 errors and existing React hook/compiler warnings.
  • yarn tsc — still fails on the repository's pre-existing src/icons/index.ts SVG module declaration errors before reaching this change.

Notes

  • Linear and Sentry MCP access were unavailable (Linear returned 401; Sentry needs auth), so the Sentry-project gate was inferred from the triage trigger title plus this repo's Sentry config (project: 'tasks').

Impact & Surface Area of Change

  • Affects API routes wrapped with withErrorHandler.
  • Expected impact: Prisma not-found/invalid UUID/unique constraint errors return normalized 404/409 responses and no longer generate generic console.error Sentry alerts when the Prisma error class identity differs at runtime.
Open in Web View Automation 

Co-authored-by: Neil Raina <makeitraina@users.noreply.github.com>
@linear-code

linear-code Bot commented Jul 3, 2026

Copy link
Copy Markdown

OUT-3676

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tasks-app Ready Ready Preview, Comment Jul 3, 2026 7:50pm

Request Review

@priosshrsth
priosshrsth marked this pull request as ready for review July 6, 2026 08:12
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the instanceof PrismaClientKnownRequestError check with a structural duck-typed guard to handle bundled/duplicate-module scenarios where class identity mismatches occur, and adds a P2002 unique-constraint → 409 Conflict mapping so duplicate-resource errors no longer surface as generic Sentry alerts.

  • Structural guard (isPrismaKnownRequestError): detects Prisma errors by checking error.name or error.constructor.name instead of instanceof, avoiding cross-bundle class-identity failures.
  • P2002 → 409 Conflict: unique constraint violations now return a 409 with a descriptive message and are demoted from console.error to console.warn, keeping them out of Sentry noise.
  • Tests: existing Prisma error cases are updated and two new tests cover the 409 path and the structural (cross-module) matching path.

Confidence Score: 4/5

Safe to merge — the two changed files are self-contained and the error-handling path is well-tested with 11 passing cases.

The duck-typed guard works correctly for all real Prisma error shapes, and the P2002 → 409 mapping is a straightforward improvement. The only concern is that the name-based matching could silently absorb a non-Prisma thrown object that happens to carry name: 'PrismaClientKnownRequestError', hiding it from Sentry — and the cross-module boundary test exercises an unusual plain-object constructor path rather than a realistic class-instance scenario. Neither issue affects the primary change, but both are worth a second look before shipping.

The isPrismaKnownRequestError guard in withErrorHandler.ts and the cross-module boundary test in withErrorHandler.test.ts deserve a closer read.

Important Files Changed

Filename Overview
src/app/api/core/utils/withErrorHandler.ts Removes the PrismaClientKnownRequestError instanceof dependency, replaces it with a structural duck-typed guard, and adds a P2002 → 409 Conflict mapping. Logic is sound; one subtle trade-off in the guard's name-based matching.
src/app/api/tests/utils/withErrorHandler.test.ts Adds P2002 → 409 test and a cross-module boundary test. Existing cases unchanged and still pass.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Handler throws error] --> B{normalizeError}
    B --> C{instanceof ZodError?}
    C -- Yes --> D[422 Unprocessable Entity]
    C -- No --> E{instanceof CopilotApiError?}
    E -- Yes --> F[CopilotAPI status/message]
    E -- No --> G{instanceof APIError?}
    G -- Yes --> H[APIError status/message]
    G -- No --> I{isPrismaKnownRequestError?}
    I -- No --> J[defaultResponse 400]
    I -- Yes --> K{getPrismaKnownRequestErrorResponse}
    K -- P2025 / invalid UUID --> L[404 Not Found]
    K -- P2002 unique constraint --> M[409 Conflict]
    K -- null unclassified --> N[defaultResponse 400]
    B --> O{shouldLogError?}
    O -- status >= 500 or not expected --> P[console.error -> Sentry alert]
    O -- isExpectedPrismaError --> Q[console.warn breadcrumb only]
    O -- isExpectedClientError non-Prisma --> R[no log]
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[Handler throws error] --> B{normalizeError}
    B --> C{instanceof ZodError?}
    C -- Yes --> D[422 Unprocessable Entity]
    C -- No --> E{instanceof CopilotApiError?}
    E -- Yes --> F[CopilotAPI status/message]
    E -- No --> G{instanceof APIError?}
    G -- Yes --> H[APIError status/message]
    G -- No --> I{isPrismaKnownRequestError?}
    I -- No --> J[defaultResponse 400]
    I -- Yes --> K{getPrismaKnownRequestErrorResponse}
    K -- P2025 / invalid UUID --> L[404 Not Found]
    K -- P2002 unique constraint --> M[409 Conflict]
    K -- null unclassified --> N[defaultResponse 400]
    B --> O{shouldLogError?}
    O -- status >= 500 or not expected --> P[console.error -> Sentry alert]
    O -- isExpectedPrismaError --> Q[console.warn breadcrumb only]
    O -- isExpectedClientError non-Prisma --> R[no log]
Loading

Reviews (1): Last reviewed commit: "Handle Prisma known request errors struc..." | Re-trigger Greptile

Comment on lines +44 to +47
return (
typeof candidate.code === 'string' &&
(candidate.name === PRISMA_KNOWN_REQUEST_ERROR_NAME || constructorName === PRISMA_KNOWN_REQUEST_ERROR_NAME)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Structural guard silent-swallows errors if name matches accidentally

The guard accepts any object with a code: string AND either name === 'PrismaClientKnownRequestError' or constructor.name === 'PrismaClientKnownRequestError'. If any non-Prisma code throws a plain object like { code: 'P2025', name: 'PrismaClientKnownRequestError' }, it will be silently treated as a known Prisma error — returning a 404 and emitting only a console.warn instead of a console.error. The result is a swallowed error that never reaches Sentry. Adding a check for at least one additional Prisma-specific property (e.g. typeof candidate.clientVersion === 'string') would tighten the guard without breaking the structural approach.

Comment on lines +153 to 169
it('recognizes Prisma known request errors across module boundaries', async () => {
const error = {
code: 'P2025',
constructor: { name: 'PrismaClientKnownRequestError' },
message: 'Record not found',
}
const handler = async (_req: NextRequest, _params: any) => {
throw error
}

const nextResponse = await withErrorHandler(handler)(req, null)
const response = await nextResponse.json()
expect(response.error).toBe('The requested resource was not found')
expect(nextResponse.status).toBe(httpStatus.NOT_FOUND)
expect(console.error).not.toHaveBeenCalled()
expect(console.warn).toHaveBeenCalledWith(error)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Cross-module boundary test exercises the wrong code path

The test object { code: 'P2025', constructor: { name: '...' } } has no name property, so it passes isPrismaKnownRequestError via the constructor-as-plain-object branch of getConstructorName. A real cross-module Prisma error is an actual class instance — its this.name is explicitly set to 'PrismaClientKnownRequestError' by Prisma, so it would match the candidate.name check, not the constructorName check. The test validates an edge case that is unlikely to occur in practice (a constructor property that is a plain object rather than a function). A more representative cross-module test would simulate an actual different-module instance where both name and a function constructor are present but instanceof fails.

@priosshrsth priosshrsth closed this Aug 3, 2026
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.

3 participants