{{ message }}
Repository navigation
Conversation
The Fetch spec gives a response an empty status message unless
something sets it: ResponseInit.statusText defaults to "", and
"initialize a response" copies it as-is. Only the scheme fetch
steps set one, to `OK` for data: and about:blank URLs and to `OK`
or `Partial Content` for blob: URLs. workerd instead derives the
RFC 9110 reason phrase from the status code, so
`new Response(null, { status: 404 }).statusText` is "Not Found"
rather than "", and likewise for Response.json() and
Response.redirect().
With pedantic_wpt enabled, an unset statusText is now "". A
Response initialized from another Response, through the
constructor or Response.json(), copies its status message
exactly. Responses returned by fetch() keep the reason phrase the
server sent, even when it matches the status code's default.
data: URL fetches now set "OK" explicitly, which is the value they
already reported. workerd does not fetch blob: or about: URLs.
The HTTP reason phrase sent for a Response returned to a client is
unchanged: an unset status message still sends the status code's
conventional phrase, so HTTP clients see no difference even though
the JS-visible statusText is empty. Error messages that include a
response's status, such as those thrown by Fetcher's put() and
get() helpers, use the same conventional phrase.
Compatibility: the observable change is gated behind the existing
pedantic_wpt flag, which has no enable date. Without it, statusText
is unchanged.
Testing: tests were not run locally because a WARP outage made
the cfdata.org dependency, remote cache and credential hosts that
edgeworker's bazel needs unreachable. CI is the verification.
Fixes #4973
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
npaun
force-pushed
the
npaun/pedantic-wpt-status-text
branch
from
October 20, 2026 04:47
a29352e to
c10c3c3
Compare
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
The Fetch spec gives a response an empty status message unless something sets it.
ResponseInit.statusTextdefaults to"", and only the scheme fetch steps set a value:OKfor data:/about:blank, andOK/Partial Contentfor blob:. workerd instead derives the reason phrase from the status code, sonew Response(null, { status: 404 }).statusTextis"Not Found", and the same goes forResponse.json()andResponse.redirect().With
pedantic_wptenabled:statusTextis"".Response.json()) copies its status message exactly.fetch()responses keep the reason phrase the server sent, even when it matches the default.data: URL fetches now set
"OK"explicitly, which is the value they already reported. workerd doesn't fetch blob: or about: URLs.The HTTP reason phrase on the wire is unchanged. An unset status message still sends the conventional phrase (new
getReasonPhrase()), so HTTP clients see no difference. Error messages that include the status, such as Fetcher'sput()/get()helpers, use the same phrase.Fixes #4973
Compatibility
Every visible change is gated on the existing
pedantic_wptflag. Without it,statusTextbehaves as before. WPT always runs withpedantic_wpt(build/wpt_test.bzl), so the statusText expected failures inresponse-init-001,response-static-redirectandresponse-static-jsonare removed from both fetch WPT configs.Open issue: statusText across JS RPC when the two Workers' flags differ
When a Response crosses JS RPC,
Response::serializesends the stored status message, and omits it entirely when it's unset.deserializerebuilds the Response without one, so the receiving Worker's flag decides what "unset" means. But "unset" means different things on each side: withoutpedantic_wptit means "use the default phrase", and with it it means"".If the two Workers'
pedantic_wptsettings differ, they see different values for the same Response:pedantic_wptnew Response(null, { status: 404 })"""Not Found"(receiver without the flag)new Response(null, { status: 404 })"Not Found"""(receiver withpedantic_wpt)fetch()result where the origin sent404 Not Found"Not Found"""(receiver withpedantic_wpt)The last row is the worst. Without the flag,
makeHttpResponsedoesn't store a reason phrase that matches the default. So apedantic_wptWorker that receives a proxied fetch result over RPC reports"", even though the origin really sentNot Found. That information is lost.Mixed settings aren't hypothetical: RPC between Workers owned by different teams is common. It also matters more if
pedantic_wptever gets an enable date, because then mixed settings become the normal state during the transition.Proposed fix: make the serialized form say exactly what the sender sees.
serializewould writegetStatusText(js), the sender's effective value, explicitly, so the receiver always sees what the sender saw. Pieces:pedantic_wptsender's unset value then arrives as an explicit"". If a receiver without the flag returns that Response to a client,getReasonPhrase()would send an empty reason phrase, because it only falls back when the value is unset.getReasonPhrase()should therefore also fall back on"". That changes the wire for an explicitly emptystatusText(today it sends an empty phrase), but HTTP clients don't depend on the reason phrase.statusText, so there's no version-skew problem.This isn't implemented in this PR yet. Feedback welcome on whether to fold it in here, before this lands.