Skip to content

Stop tracing Next.js control-flow errors as span errors#438

Open
eps1lon wants to merge 3 commits into
mainfrom
sebbie/tracing-skip-internal-next-errors
Open

Stop tracing Next.js control-flow errors as span errors#438
eps1lon wants to merge 3 commits into
mainfrom
sebbie/tracing-skip-internal-next-errors

Conversation

@eps1lon

@eps1lon eps1lon commented Jul 22, 2026

Copy link
Copy Markdown
Member

Follow-up to #397. Flags SDK was still reporting errored OTEL spans even though the error is considered handled from Next.js' perspective. The OTEL spans with errors could create alerts even though everything would be working correctly.

The `trace()` helper marked spans as errored via `span.setStatus({ code: 2, message })` for every rejection of the traced function. This included errors that Next.js uses for control flow and that the evaluation layer deliberately re-throws via `isInternalNextError`: redirects, notFound, and the rejected hanging promises of aborted prerenders (`HANGING_PROMISE_REJECTION`).

The hanging-promise case is the noisy one in practice. Whenever a runtime prefetch prerender is aborted while a flag evaluation awaits `connection()` or `cookies()` (for example through an `identify` function that reads the request), the evaluation promise rejects with the `HANGING_PROMISE_REJECTION` digest, and every affected flag span reported "During prerendering, `connection()` rejects when the prerender is complete" as its status description. On heavily prefetched routes this produces a large volume of error-annotated spans for behavior that is entirely expected.

The tracing helper now skips the error status for internal Next.js errors in both the promise rejection path and the synchronous throw path, while still ending the span and preserving span-context attributes. Control flow is unchanged: the error keeps propagating to the caller. `isInternalNextError` moves from `src/next/` to `src/lib/` so the shared tracing module can use it without depending on the Next.js entrypoint; it is a plain digest check with no Next.js imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
flags-playground Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm
flags-sdk-dev Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm
flags-sdk-snippets Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm
flags-sdk-sveltekit-snippets Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm
shirt-shop Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm
shirt-shop-api Ready Ready Preview, Comment, Open in v0 Jul 23, 2026 8:28pm

Comment thread packages/flags/src/lib/tracing.ts Outdated
Comment thread packages/flags/src/lib/tracing.ts Outdated
message: error instanceof Error ? error.message : undefined,
});
span.setStatus({
code: 2, // 2 = Error

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Span status is now UNSET for ignored errors. When isInternalNextError is true you skip setStatus entirely, so the span ends with no status rather than OK. That matches the changeset ("end without an error status") and is defensible — a redirect isn't really "Ok" either — but if you'd rather these read as successful in trace UIs, set code: 1. Worth a deliberate decision rather than falling out of the control flow.

@eps1lon wdyt, should hat set code: 1?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah all of these are ok. We just use them for control flow. So if code:1 shows as successful in the UI, we should use that.

When `isIgnoredError` classifies an error as control flow, the traced function completed as intended, so the span now gets an explicit Ok status instead of ending with an unset status. This keeps ignored control-flow rejections (like Next.js hanging promise rejections of aborted prerenders) clearly distinguishable from both errored spans and spans that were never finalized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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