Problem
While working on the Node 26 / global dispatcher compatibility backport, we found that test/node-test/client-errors.js only passed on Node 26 after the test was relaxed on main to tolerate extra/reordered POST attempts.
That relaxation appears to mask a real client bug.
The strict invariant from the original test is:
- a streaming POST body writes
"a string" and then errors with kaboom
- that POST fails
- the following queued GET reconnects and succeeds
- the failed POST must not be retried before the queued GET
On Node 26, with the original strict test, the server can observe a POST where it expected the queued GET. The current main test handles this by tearing down extra POST attempts, but that accepts behavior the test was meant to reject.
Root cause
In the HTTP/1 write path, undici probes Node streams before installing the writer/error handling:
if (body && typeof body.read === 'function') {
// Try to read EOF in order to get length.
body.read(0)
}
For user-provided Readable bodies, body.read(0) can synchronously invoke the stream's read() implementation. If that implementation pushes data and errors, the body error happens before undici has attached the request body writer/error handlers. This changes the order of events on Node 26 and lets the queued GET proceed as if it were the first request, while the original POST may still be observed/retried unexpectedly.
Suggested fix
Forward-port the v7.x fix from PR #5319:
- remove the eager
body.read(0) probe from lib/dispatcher/client-h1.js
- restore
test/node-test/client-errors.js to assert the strict order instead of tolerating additional POST attempts
- remove the helper behavior that destroys extra POST requests in that test
The test should fail fast if the request following the failed POST is not the queued GET.
Validation from the v7.x backport
With the eager body.read(0) removed and strict client-errors expectations restored, the following passed locally:
- Node 26:
node --test --test-reporter=spec --test-timeout=180000 test/node-test/client-errors.js
- Node 25: same test file
- Node 20: same test file
npm run test:node-test
Problem
While working on the Node 26 / global dispatcher compatibility backport, we found that
test/node-test/client-errors.jsonly passed on Node 26 after the test was relaxed onmainto tolerate extra/reordered POST attempts.That relaxation appears to mask a real client bug.
The strict invariant from the original test is:
"a string"and then errors withkaboomOn Node 26, with the original strict test, the server can observe a POST where it expected the queued GET. The current
maintest handles this by tearing down extra POST attempts, but that accepts behavior the test was meant to reject.Root cause
In the HTTP/1 write path, undici probes Node streams before installing the writer/error handling:
For user-provided
Readablebodies,body.read(0)can synchronously invoke the stream'sread()implementation. If that implementation pushes data and errors, the body error happens before undici has attached the request body writer/error handlers. This changes the order of events on Node 26 and lets the queued GET proceed as if it were the first request, while the original POST may still be observed/retried unexpectedly.Suggested fix
Forward-port the v7.x fix from PR #5319:
body.read(0)probe fromlib/dispatcher/client-h1.jstest/node-test/client-errors.jsto assert the strict order instead of tolerating additional POST attemptsThe test should fail fast if the request following the failed POST is not the queued GET.
Validation from the v7.x backport
With the eager
body.read(0)removed and strictclient-errorsexpectations restored, the following passed locally:node --test --test-reporter=spec --test-timeout=180000 test/node-test/client-errors.jsnpm run test:node-test