Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/lib/sentry/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ function isTransientTrpcHtmlError(error: unknown): boolean {
return message.includes("Unexpected token '<'") || message.includes("<!DOCTYPE")
}

/** Turso/libSQL blips — retried via saferFetch; still skip if all attempts fail. */
function isTransientTursoPrismaError(error: unknown): boolean {
const message = getErrorMessage(error)
return (
message.includes("SERVER_ERROR") &&
(message.includes("HTTP status 502") ||
message.includes("HTTP status 503") ||
message.includes("HTTP status 504"))
)
}

/** Client fetch failures that often succeed on retry (offline blip, tab sleep, CDN hiccup). */
export function isRetriableNetworkError(error: unknown): boolean {
const message = getErrorMessage(error)
Expand Down Expand Up @@ -281,6 +292,10 @@ export function shouldSkipCapture(error: unknown): boolean {
return true
}

if (isTransientTursoPrismaError(error)) {
return true
}

if (error instanceof RaidHubError && HANDLED_RAIDHUB_ERROR_CODES.has(error.errorCode)) {
return true
}
Expand Down Expand Up @@ -428,5 +443,14 @@ export function shouldDropClientEvent(event: ErrorEvent): boolean {
return true
}

// Minified-chunk parse failures on old browsers / bots (no app frames).
if (
!hasAppStackFrame &&
text.includes("SyntaxError") &&
text.includes("Unexpected token '{'")
) {
return true
}

return false
}
22 changes: 21 additions & 1 deletion src/lib/sentry/shared-options.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import type { TransactionEvent } from "@sentry/core"
import type { ErrorEvent, EventHint } from "@sentry/nextjs"

/** Next.js App Router control-flow signals — not application bugs. */
const NEXTJS_CONTROL_FLOW_ERRORS = ["NEXT_NOT_FOUND", "NEXT_REDIRECT"] as const

/** Standalone Prisma spans sampled as transactions — Turso latency noise, not app bugs. */
export function shouldDropSentryTransaction(event: TransactionEvent): boolean {
const transaction =
typeof event === "object" && event !== null && "transaction" in event
? (event as { transaction?: unknown }).transaction
: undefined

return transaction === "prisma:client:operation"
}

export function shouldDropSentryEvent(event: ErrorEvent, _hint?: EventHint): boolean {
const values = event.exception?.values
if (!values?.length) {
Expand All @@ -19,5 +30,14 @@ export const sentrySharedOptions = {
beforeSend(event: ErrorEvent, hint: EventHint) {
return shouldDropSentryEvent(event, hint) ? null : event
},
ignoreErrors: [...NEXTJS_CONTROL_FLOW_ERRORS]
beforeSendTransaction(event: TransactionEvent) {
if (shouldDropSentryTransaction(event)) {
return null
}

// TransactionEvent from @sentry/nextjs is `any` in ESLint; tsc expects the full event back.
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- Sentry SDK transaction callback
return event
},
ignoreErrors: [...NEXTJS_CONTROL_FLOW_ERRORS, "Unexpected token '{'"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The ignoreErrors option for "Unexpected token '{'" unconditionally drops errors, preventing the beforeSend handler from correctly preserving legitimate app-frame errors.
Severity: MEDIUM

Suggested Fix

Remove "Unexpected token '{'" from the ignoreErrors array in src/lib/sentry/shared-options.ts. The filtering logic for this specific error is already handled correctly within the beforeSend handler (shouldDropClientEvent), which properly checks for the presence of an application stack frame before deciding whether to drop the event. This change will ensure all filtering for this error is consolidated in one place.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/lib/sentry/shared-options.ts#L34

Potential issue: The `ignoreErrors` configuration in `sentrySharedOptions` includes
`"Unexpected token '{'"`. This creates an unconditional filter that runs early in
Sentry's event processing pipeline. As a result, any error with this message is dropped
before the `beforeSend` handler is called. This contradicts the logic in
`shouldDropClientEvent`, which is designed to only drop these errors if they lack an
application stack frame. Consequently, legitimate application errors, such as a
`JSON.parse` failure in `BungieClient.ts`, could be silently ignored instead of being
reported for debugging.

Did we get this right? 👍 / 👎 to inform future reviews.

}
22 changes: 20 additions & 2 deletions src/lib/server/saferFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ const retriableErrorCauseStrings = [
"ECONNRESET",
"fetch failed",
"network error",
"Connection terminated unexpectedly"
"Connection terminated unexpectedly",
"HTTP status 502",
"HTTP status 503",
"HTTP status 504"
]

const retriableHttpStatuses = new Set([502, 503, 504])

function collectErrorMessages(err: unknown): string[] {
const messages: string[] = []
let current: unknown = err
Expand Down Expand Up @@ -82,11 +87,24 @@ const fetchWithBodyClone: typeof fetch = async (request, options) => {
return fetch(request, options)
}

async function fetchWithRetriableStatuses(
request: RequestInfo | URL,
options?: RequestInit
): Promise<Response> {
const response = await fetchWithBodyClone(request, options)

if (retriableHttpStatuses.has(response.status)) {
throw new Error(`Server returned HTTP status ${response.status}`)
}

return response
}

export const saferFetch = withRetries(
{
maxAttempts: 5,
backoff: attempt => attempt ** 2 * 5,
retryOn: isRetriableFetchError
},
fetchWithBodyClone
fetchWithRetriableStatuses
)
Loading