-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(nextjs): Correct in_app for Turbopack dev server stack frames #23248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import test, { expect } from '@playwright/test'; | ||
| import { waitForError } from '@sentry-internal/test-utils'; | ||
| import { isTurbopackDevMode } from './isDevMode'; | ||
|
|
||
| // Regression test for https://github.com/getsentry/sentry-javascript/issues/23176 | ||
| // | ||
| // Under `next dev` with Turbopack, server stack frames get their `in_app` classification inverted: | ||
| // Turbopack names vendor chunks `node_modules_<pkg>_<hash>.js` (underscores, no `node_modules/` | ||
| // segment) and prefixes app modules with `[project]/`. `filenameIsInApp` keys off a literal | ||
| // `node_modules/` substring and treats bracket-prefixed paths as internal, so vendor code is | ||
| // marked `in_app: true` and the real app crash site is marked `in_app: false`. | ||
| test('Turbopack dev: server stack frames are classified in_app correctly', async ({ page }) => { | ||
| test.skip( | ||
| !isTurbopackDevMode, | ||
| 'Turbopack chunk naming (node_modules_ / [project]) only occurs in Turbopack dev mode', | ||
| ); | ||
|
|
||
| const errorPromise = waitForError('nextjs-16', errorEvent => { | ||
| return !!errorEvent?.exception?.values?.some(value => value.value?.includes('Tool call failed')); | ||
| }); | ||
|
|
||
| await page.goto('/ai-error-test'); | ||
|
|
||
| const errorEvent = await errorPromise; | ||
|
|
||
| const frames = errorEvent.exception?.values?.flatMap(value => value.stacktrace?.frames ?? []) ?? []; | ||
| expect(frames.length).toBeGreaterThan(0); | ||
|
|
||
| // The crash site is app code: the `execute` tool callback defined in `app/ai-error-test/page.tsx`. | ||
| // It must be `in_app` so the issue leads with the real crash frame instead of vendor code. | ||
| const appFrame = frames.find(frame => frame.filename?.includes('ai-error-test')); | ||
| expect( | ||
| appFrame, | ||
| `expected an app frame for the route file, got: ${JSON.stringify(frames.map(frame => frame.filename))}`, | ||
| ).toBeDefined(); | ||
| expect(appFrame?.in_app).toBe(true); | ||
|
|
||
| // Vendor frames from the `ai` package (Turbopack: `...node_modules_ai_...`) must not be `in_app`. | ||
| const vendorFrames = frames.filter(frame => frame.filename?.includes('node_modules')); | ||
| for (const vendorFrame of vendorFrames) { | ||
| expect(vendorFrame.in_app, `vendor frame ${vendorFrame.filename} should not be in_app`).toBe(false); | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,15 +121,20 @@ export async function devErrorSymbolicationEventProcessor(event: Event, hint: Ev | |
| resolvedFrame.originalCodeFrame, | ||
| ); | ||
|
|
||
| const resolvedFilename = resolvedFrame.originalStackFrame.file | ||
| ? stripWebpackInternalPrefix(resolvedFrame.originalStackFrame.file) | ||
| : undefined; | ||
|
|
||
| return { | ||
| ...frame, | ||
| pre_context: preContextLines, | ||
| context_line: contextLine, | ||
| post_context: postContextLines, | ||
| function: resolvedFrame.originalStackFrame.methodName, | ||
| filename: resolvedFrame.originalStackFrame.file | ||
| ? stripWebpackInternalPrefix(resolvedFrame.originalStackFrame.file) | ||
| : undefined, | ||
| filename: resolvedFilename, | ||
| // The parse-time `in_app` is derived from Turbopack's dev chunk names (`node_modules_<pkg>_<hash>.js`, | ||
| // `[project]/…`), which invert the classification. Re-derive it from the resolved original source path. | ||
| in_app: resolvedFilename ? !resolvedFilename.includes('node_modules') : frame.in_app, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the filenames always starting with this string? Then we could tighten this to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to make that assumption since it may rely on the project being in a workspace or something so the starting path wouldn't be Also noticed that |
||
| lineno: | ||
| resolvedFrame.originalStackFrame.lineNumber || resolvedFrame.originalStackFrame.line1 || undefined, | ||
| colno: resolvedFrame.originalStackFrame.column || resolvedFrame.originalStackFrame.column1 || undefined, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vacuous vendor in_app assertion
Low Severity
The vendor
in_appchecks can pass without verifying any frames:vendorFramesis filtered and asserted in a loop, but nothing requires that filter to match. That weakens coverage for the half of this regression where vendor frames were wrongly markedin_app. Sibling tests in this app assert filtered collections are non-empty before checking their contents. Flagged because the PR review rules ask for thorough assertions of newly added behavior in fix tests.Triggered by project rule: PR Review Guidelines for Cursor Bot
Reviewed by Cursor Bugbot for commit e0d8aed. Configure here.