[Flight & DevTools] Strip the whole "async " prefix from V8 stack frame names - #37608
Merged
hoxyq merged 2 commits intoSep 13, 2026
Merged
Conversation
V8 prefixes async call sites with "async ", as in
`at async Comments (app.js:5:9236)`. The chrome stack parser detects
that prefix with `startsWith('async ')` but then cuts it with
`slice(5)`, which is one character short of the six in `'async '`. The
leading space survives, so the frame name comes out as `" Comments"`
instead of `"Comments"`.
That name is what `extractLocationFromComponentStack` and
`extractLocationFromOwnerStack` put in the `ReactFunctionLocation` they
hand back to the DevTools fiber renderer, so any async component ends
up recorded under a name with a stray leading space.
Added a case to the existing `extractLocationFromComponentStack`
tests that fails before the change and passes after.
hoxyq
approved these changes
Sep 13, 2026
hoxyq
left a comment
Collaborator
There was a problem hiding this comment.
Thank you!
Could you please also fix https://github.com/react/react/blob/main/packages/react-server/src/ReactFlightStackConfigV8.js#L239-L242 in this PR, or open a separate one?
ReactFlightStackConfigV8 has the same off-by-one as the DevTools copy:
when it falls back to parsing a formatted stack, it detects the prefix
with `startsWith('async ')` and then cuts it with `slice(5)`, so an
async frame comes back named `" outerName"`.
That fallback runs when the error's stack was already read or assigned
before React gets to it. Added a test that assigns a formatted stack and
checks the parsed frames; it fails before the change and passes after.
Contributor
Author
|
@hoxyq Done in this PR: 1d7a34f changes the same In that file the string parser only runs as a fallback, when the error's |
hoxyq
approved these changes
Sep 13, 2026
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 13, 2026
…me names (#37608) ## Summary V8 prefixes async call sites with `async ` when it prints a stack, like this: ``` Error: boom at inner (/tmp/asy.js:1:44) at async outerName (/tmp/asy.js:2:30) ``` `parseStackTraceFromChromeStack` captures `async outerName` as the frame name and strips the prefix here: ```js } else if (name.startsWith('async ')) { name = name.slice(5); isAsync = true; } ``` `'async '` is six characters, so `slice(5)` leaves the space behind and the frame name comes back as `' outerName'` rather than `'outerName'`. I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the `ReactFunctionLocation` returned by `extractLocationFromComponentStack` and `extractLocationFromOwnerStack`, which `backend/fiber/renderer.js` stores as `instance.source`. Any async component whose frame reaches that path is recorded under a name with a stray leading space. The same line exists in `packages/react-server/src/ReactFlightStackConfigV8.js`, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error's `stack` was read or assigned before React reaches it. #37130 is open on that file too, but it does not touch these lines. ## How did you test this change? I first confirmed the format V8 actually emits, rather than assuming it: ``` $ node -e 'async function inner(){await null;throw new Error("boom")} async function outerName(){await inner()} outerName().catch(e=>console.log(e.stack))' Error: boom at inner ([eval]:1:52) at async outerName ([eval]:2:33) ``` Then I added a case to the existing `extractLocationFromComponentStack` block in `utils-test.js`. Against `main` it fails with exactly the leading space: ``` ● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name - Expected - 1 + Received + 1 Array [ - "Comments", + " Comments", "https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js", 5, 9236, ] ``` With the one-character fix applied: ``` $ yarn test --build --project=devtools -r=experimental utils-test PASS packages/react-devtools-shared/src/__tests__/utils-test.js Tests: 63 passed, 63 total ``` I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at `9 failed, 4 failed suites`, the same test names each time (`componentStacks`, `console`, `inspectedElement`, `legacy/inspectElement`), so those failures are pre-existing on `main` in my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after. For the Flight side I added `ReactFlightStackConfigV8-test.js`, which assigns a formatted stack to an error and checks what `parseStackTrace` returns. Against `main` it fails with the same leading space (`" outerName"`); with the fix it passes on stable and experimental in development. It is gated to `__DEV__` because that fallback goes through the DEV-only stack cache. `ReactFlightServer-test` and `ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests). `prettier` and `eslint` are clean on all changed files. `yarn flow dom-node` reported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change. AI tools used DiffTrain build for [ccea5fd](ccea5fd)
github-actions Bot
pushed a commit
to code/lib-react
that referenced
this pull request
Sep 13, 2026
…me names (react#37608) ## Summary V8 prefixes async call sites with `async ` when it prints a stack, like this: ``` Error: boom at inner (/tmp/asy.js:1:44) at async outerName (/tmp/asy.js:2:30) ``` `parseStackTraceFromChromeStack` captures `async outerName` as the frame name and strips the prefix here: ```js } else if (name.startsWith('async ')) { name = name.slice(5); isAsync = true; } ``` `'async '` is six characters, so `slice(5)` leaves the space behind and the frame name comes back as `' outerName'` rather than `'outerName'`. I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the `ReactFunctionLocation` returned by `extractLocationFromComponentStack` and `extractLocationFromOwnerStack`, which `backend/fiber/renderer.js` stores as `instance.source`. Any async component whose frame reaches that path is recorded under a name with a stray leading space. The same line exists in `packages/react-server/src/ReactFlightStackConfigV8.js`, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error's `stack` was read or assigned before React reaches it. react#37130 is open on that file too, but it does not touch these lines. ## How did you test this change? I first confirmed the format V8 actually emits, rather than assuming it: ``` $ node -e 'async function inner(){await null;throw new Error("boom")} async function outerName(){await inner()} outerName().catch(e=>console.log(e.stack))' Error: boom at inner ([eval]:1:52) at async outerName ([eval]:2:33) ``` Then I added a case to the existing `extractLocationFromComponentStack` block in `utils-test.js`. Against `main` it fails with exactly the leading space: ``` ● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name - Expected - 1 + Received + 1 Array [ - "Comments", + " Comments", "https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js", 5, 9236, ] ``` With the one-character fix applied: ``` $ yarn test --build --project=devtools -r=experimental utils-test PASS packages/react-devtools-shared/src/__tests__/utils-test.js Tests: 63 passed, 63 total ``` I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at `9 failed, 4 failed suites`, the same test names each time (`componentStacks`, `console`, `inspectedElement`, `legacy/inspectElement`), so those failures are pre-existing on `main` in my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after. For the Flight side I added `ReactFlightStackConfigV8-test.js`, which assigns a formatted stack to an error and checks what `parseStackTrace` returns. Against `main` it fails with the same leading space (`" outerName"`); with the fix it passes on stable and experimental in development. It is gated to `__DEV__` because that fallback goes through the DEV-only stack cache. `ReactFlightServer-test` and `ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests). `prettier` and `eslint` are clean on all changed files. `yarn flow dom-node` reported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change. AI tools used DiffTrain build for [ccea5fd](react@ccea5fd)
github-actions Bot
pushed a commit
to code/lib-react
that referenced
this pull request
Sep 13, 2026
…me names (react#37608) ## Summary V8 prefixes async call sites with `async ` when it prints a stack, like this: ``` Error: boom at inner (/tmp/asy.js:1:44) at async outerName (/tmp/asy.js:2:30) ``` `parseStackTraceFromChromeStack` captures `async outerName` as the frame name and strips the prefix here: ```js } else if (name.startsWith('async ')) { name = name.slice(5); isAsync = true; } ``` `'async '` is six characters, so `slice(5)` leaves the space behind and the frame name comes back as `' outerName'` rather than `'outerName'`. I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the `ReactFunctionLocation` returned by `extractLocationFromComponentStack` and `extractLocationFromOwnerStack`, which `backend/fiber/renderer.js` stores as `instance.source`. Any async component whose frame reaches that path is recorded under a name with a stray leading space. The same line exists in `packages/react-server/src/ReactFlightStackConfigV8.js`, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error's `stack` was read or assigned before React reaches it. react#37130 is open on that file too, but it does not touch these lines. ## How did you test this change? I first confirmed the format V8 actually emits, rather than assuming it: ``` $ node -e 'async function inner(){await null;throw new Error("boom")} async function outerName(){await inner()} outerName().catch(e=>console.log(e.stack))' Error: boom at inner ([eval]:1:52) at async outerName ([eval]:2:33) ``` Then I added a case to the existing `extractLocationFromComponentStack` block in `utils-test.js`. Against `main` it fails with exactly the leading space: ``` ● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name - Expected - 1 + Received + 1 Array [ - "Comments", + " Comments", "https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js", 5, 9236, ] ``` With the one-character fix applied: ``` $ yarn test --build --project=devtools -r=experimental utils-test PASS packages/react-devtools-shared/src/__tests__/utils-test.js Tests: 63 passed, 63 total ``` I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at `9 failed, 4 failed suites`, the same test names each time (`componentStacks`, `console`, `inspectedElement`, `legacy/inspectElement`), so those failures are pre-existing on `main` in my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after. For the Flight side I added `ReactFlightStackConfigV8-test.js`, which assigns a formatted stack to an error and checks what `parseStackTrace` returns. Against `main` it fails with the same leading space (`" outerName"`); with the fix it passes on stable and experimental in development. It is gated to `__DEV__` because that fallback goes through the DEV-only stack cache. `ReactFlightServer-test` and `ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests). `prettier` and `eslint` are clean on all changed files. `yarn flow dom-node` reported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change. AI tools used DiffTrain build for [ccea5fd](react@ccea5fd)
github-actions Bot
pushed a commit
to seshan18/react
that referenced
this pull request
Sep 13, 2026
…me names (react#37608) ## Summary V8 prefixes async call sites with `async ` when it prints a stack, like this: ``` Error: boom at inner (/tmp/asy.js:1:44) at async outerName (/tmp/asy.js:2:30) ``` `parseStackTraceFromChromeStack` captures `async outerName` as the frame name and strips the prefix here: ```js } else if (name.startsWith('async ')) { name = name.slice(5); isAsync = true; } ``` `'async '` is six characters, so `slice(5)` leaves the space behind and the frame name comes back as `' outerName'` rather than `'outerName'`. I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the `ReactFunctionLocation` returned by `extractLocationFromComponentStack` and `extractLocationFromOwnerStack`, which `backend/fiber/renderer.js` stores as `instance.source`. Any async component whose frame reaches that path is recorded under a name with a stray leading space. The same line exists in `packages/react-server/src/ReactFlightStackConfigV8.js`, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error's `stack` was read or assigned before React reaches it. react#37130 is open on that file too, but it does not touch these lines. ## How did you test this change? I first confirmed the format V8 actually emits, rather than assuming it: ``` $ node -e 'async function inner(){await null;throw new Error("boom")} async function outerName(){await inner()} outerName().catch(e=>console.log(e.stack))' Error: boom at inner ([eval]:1:52) at async outerName ([eval]:2:33) ``` Then I added a case to the existing `extractLocationFromComponentStack` block in `utils-test.js`. Against `main` it fails with exactly the leading space: ``` ● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name - Expected - 1 + Received + 1 Array [ - "Comments", + " Comments", "https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js", 5, 9236, ] ``` With the one-character fix applied: ``` $ yarn test --build --project=devtools -r=experimental utils-test PASS packages/react-devtools-shared/src/__tests__/utils-test.js Tests: 63 passed, 63 total ``` I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at `9 failed, 4 failed suites`, the same test names each time (`componentStacks`, `console`, `inspectedElement`, `legacy/inspectElement`), so those failures are pre-existing on `main` in my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after. For the Flight side I added `ReactFlightStackConfigV8-test.js`, which assigns a formatted stack to an error and checks what `parseStackTrace` returns. Against `main` it fails with the same leading space (`" outerName"`); with the fix it passes on stable and experimental in development. It is gated to `__DEV__` because that fallback goes through the DEV-only stack cache. `ReactFlightServer-test` and `ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests). `prettier` and `eslint` are clean on all changed files. `yarn flow dom-node` reported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change. AI tools used DiffTrain build for [ccea5fd](react@ccea5fd)
github-actions Bot
pushed a commit
to seshan18/react
that referenced
this pull request
Sep 13, 2026
…me names (react#37608) ## Summary V8 prefixes async call sites with `async ` when it prints a stack, like this: ``` Error: boom at inner (/tmp/asy.js:1:44) at async outerName (/tmp/asy.js:2:30) ``` `parseStackTraceFromChromeStack` captures `async outerName` as the frame name and strips the prefix here: ```js } else if (name.startsWith('async ')) { name = name.slice(5); isAsync = true; } ``` `'async '` is six characters, so `slice(5)` leaves the space behind and the frame name comes back as `' outerName'` rather than `'outerName'`. I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the `ReactFunctionLocation` returned by `extractLocationFromComponentStack` and `extractLocationFromOwnerStack`, which `backend/fiber/renderer.js` stores as `instance.source`. Any async component whose frame reaches that path is recorded under a name with a stray leading space. The same line exists in `packages/react-server/src/ReactFlightStackConfigV8.js`, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error's `stack` was read or assigned before React reaches it. react#37130 is open on that file too, but it does not touch these lines. ## How did you test this change? I first confirmed the format V8 actually emits, rather than assuming it: ``` $ node -e 'async function inner(){await null;throw new Error("boom")} async function outerName(){await inner()} outerName().catch(e=>console.log(e.stack))' Error: boom at inner ([eval]:1:52) at async outerName ([eval]:2:33) ``` Then I added a case to the existing `extractLocationFromComponentStack` block in `utils-test.js`. Against `main` it fails with exactly the leading space: ``` ● utils › extractLocationFromComponentStack › should strip the async prefix from a frame name - Expected - 1 + Received + 1 Array [ - "Comments", + " Comments", "https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js", 5, 9236, ] ``` With the one-character fix applied: ``` $ yarn test --build --project=devtools -r=experimental utils-test PASS packages/react-devtools-shared/src/__tests__/utils-test.js Tests: 63 passed, 63 total ``` I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at `9 failed, 4 failed suites`, the same test names each time (`componentStacks`, `console`, `inspectedElement`, `legacy/inspectElement`), so those failures are pre-existing on `main` in my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after. For the Flight side I added `ReactFlightStackConfigV8-test.js`, which assigns a formatted stack to an error and checks what `parseStackTrace` returns. Against `main` it fails with the same leading space (`" outerName"`); with the fix it passes on stable and experimental in development. It is gated to `__DEV__` because that fallback goes through the DEV-only stack cache. `ReactFlightServer-test` and `ReactFlightAsyncDebugInfo-test` still pass next to it (23 tests). `prettier` and `eslint` are clean on all changed files. `yarn flow dom-node` reported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change. AI tools used DiffTrain build for [ccea5fd](react@ccea5fd)
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
V8 prefixes async call sites with
asyncwhen it prints a stack, like this:parseStackTraceFromChromeStackcapturesasync outerNameas the frame name and strips the prefix here:'async 'is six characters, soslice(5)leaves the space behind and the frame name comes back as' outerName'rather than'outerName'.I noticed it while reading the parser, and it is not purely cosmetic: that name is the first element of the
ReactFunctionLocationreturned byextractLocationFromComponentStackandextractLocationFromOwnerStack, whichbackend/fiber/renderer.jsstores asinstance.source. Any async component whose frame reaches that path is recorded under a name with a stray leading space.The same line exists in
packages/react-server/src/ReactFlightStackConfigV8.js, which the DevTools file is a copy of. After review I fixed it in this PR as well, in a second commit. There it only matters on the fallback path that parses an already formatted stack string, when the error'sstackwas read or assigned before React reaches it. #37130 is open on that file too, but it does not touch these lines.How did you test this change?
I first confirmed the format V8 actually emits, rather than assuming it:
Then I added a case to the existing
extractLocationFromComponentStackblock inutils-test.js. Againstmainit fails with exactly the leading space:With the one-character fix applied:
I also ran the whole DevTools project before and after to check I was not moving anything else. Both runs end at
9 failed, 4 failed suites, the same test names each time (componentStacks,console,inspectedElement,legacy/inspectElement), so those failures are pre-existing onmainin my environment and unrelated to this change. The only difference between the two runs is my new test: 586 passed before, 587 after.For the Flight side I added
ReactFlightStackConfigV8-test.js, which assigns a formatted stack to an error and checks whatparseStackTracereturns. Againstmainit fails with the same leading space (" outerName"); with the fix it passes on stable and experimental in development. It is gated to__DEV__because that fallback goes through the DEV-only stack cache.ReactFlightServer-testandReactFlightAsyncDebugInfo-teststill pass next to it (23 tests).prettierandeslintare clean on all changed files.yarn flow dom-nodereported no errors for the DevTools commit; I did not rerun Flow after the one-character Flight change.AI tools used