Skip to content

Stop a body-less response from killing the whole sitespeed run - #1610

Merged
marcusosterberg merged 1 commit into
mainfrom
fix/sitespeed-fetch-interception-crash
Sep 23, 2026
Merged

marcusosterberg merged 1 commit into
mainfrom
fix/sitespeed-fetch-interception-crash

Conversation

@marcusosterberg

Copy link
Copy Markdown
Contributor

chrome-custom.cjs enables CDP Fetch interception on Documents and then called Fetch.getResponseBody unconditionally. When the response has no retrievable body the call rejects with "Can only get response body on requests captured after headers received". The listener is async, so that rejection is unhandled, and under Node 24 an unhandled rejection terminates the process - which is the sitespeed process. One awkward response therefore lost the entire test run, not just that request.

Reproducer: any URL answering 401 with content-length 0, for example an Azure App Service behind Entra auth. Test 15 died after ~7 seconds and scored -1.

Fixes:

  • Release requests paused before a response exists (network error, or no responseStatusCode); getResponseBody cannot succeed for those.
  • Guard getResponseBody and fulfillRequest, releasing the request instead of leaking a rejection.
  • Scope bodyResult with let. It was an implicit global assigned across an await, so concurrent Document interceptions could read each other's body.

Fetch.continueRequest is used to release in both stages; verified against Chromium 150 that it resolves a Response-stage pause (8/8). A pause that is never released would stall the page load, so the failure is logged rather than swallowed.

Verified: the 401 site now completes and scores instead of crashing. On a healthy site the new branches never execute and the rating is unchanged (4.49 vs 4.47, run-to-run variance). With a sitespeed-rules.json present, header and HTML rewriting still applies identically (2 header injections, 2 HTML rewrites, 0 crashes, before and after).

chrome-custom.cjs enables CDP Fetch interception on Documents and then
called Fetch.getResponseBody unconditionally. When the response has no
retrievable body the call rejects with "Can only get response body on
requests captured after headers received". The listener is async, so that
rejection is unhandled, and under Node 24 an unhandled rejection
terminates the process - which is the sitespeed process. One awkward
response therefore lost the entire test run, not just that request.

Reproducer: any URL answering 401 with content-length 0, for example an
Azure App Service behind Entra auth. Test 15 died after ~7 seconds and
scored -1.

Fixes:
- Release requests paused before a response exists (network error, or no
  responseStatusCode); getResponseBody cannot succeed for those.
- Guard getResponseBody and fulfillRequest, releasing the request instead
  of leaking a rejection.
- Scope bodyResult with let. It was an implicit global assigned across an
  await, so concurrent Document interceptions could read each other's body.

Fetch.continueRequest is used to release in both stages; verified against
Chromium 150 that it resolves a Response-stage pause (8/8). A pause that is
never released would stall the page load, so the failure is logged rather
than swallowed.

Verified: the 401 site now completes and scores instead of crashing. On a
healthy site the new branches never execute and the rating is unchanged
(4.49 vs 4.47, run-to-run variance). With a sitespeed-rules.json present,
header and HTML rewriting still applies identically (2 header injections,
2 HTML rewrites, 0 crashes, before and after).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@marcusosterberg
marcusosterberg merged commit 6ce4113 into main Sep 23, 2026
20 of 24 checks passed
@marcusosterberg
marcusosterberg deleted the fix/sitespeed-fetch-interception-crash branch September 23, 2026 20:38
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.

1 participant