Skip to content

fix: avoid 401 failures for stream-backed request bodies#4941

Open
mcollina wants to merge 1 commit intomainfrom
fix/401-stream-compliant
Open

fix: avoid 401 failures for stream-backed request bodies#4941
mcollina wants to merge 1 commit intomainfrom
fix/401-stream-compliant

Conversation

@mcollina
Copy link
Copy Markdown
Member

This relates to...

Summary

This PR fixes a bug where fetch() throws TypeError: fetch failed for POST/PUT requests with ReadableStream bodies when the server responds with a 401 status code.

Root Cause

The issue occurred in the 401 HTTP authentication handling code in httpNetworkOrCacheFetch(). When:

  1. A request with a ReadableStream body receives a 401 response
  2. isTraversableNavigable() returned true (a TODO stub)

The code would enter the 401 retry block and check request.body.source == null, which is true for ReadableStreams, causing it to throw a network error before the return response fix from #4769 could be reached.

The Fix

1. Updated isTraversableNavigable() (lib/web/fetch/util.js)

Changed from always returning true to properly checking if we have an actual navigable object:

function isTraversableNavigable (navigable) {
  // Returns true only if we have an actual traversable navigable object
  // that can prompt the user for credentials. In Node.js, this will always
  // be false since there's no Window object or navigable.
  return navigable != null && navigable !== 'client' && navigable !== 'no-traversable'
}

This ensures that in Node.js (which has no Window/navigable), the 401 retry block is never entered, and the response is returned directly.

2. Changed network error to return response (lib/web/fetch/index.js)

For the edge case where isTraversableNavigable() returns true (in browser environments with actual navigables), changed the body source check to return the response instead of a network error:

if (request.body.source == null) {
  return response  // Previously: return makeNetworkError('expected non-null body source')
}

This is more spec-compliant as it aligns with the Fetch spec discussion in whatwg/fetch#1132, which allows implementations flexibility when credentials can't be obtained.

Spec Compliance

The current undici behavior was not fully spec-compliant. The Fetch specification:

  1. Allows implementations flexibility in handling 401 responses when credentials can't be obtained
  2. Discusses returning opaque responses or the 401 response directly (see Allow more flexibility in how 401s/407s are handled? whatwg/fetch#1132)
  3. Does not mandate throwing a network error for stream-backed bodies when not actually retrying

This fix brings undici closer to spec compliance by:

  • Properly implementing isTraversableNavigable() to distinguish between browser and Node.js contexts
  • Returning the 401 response directly when credentials can't be obtained (rather than throwing a network error)
  • Only entering the retry logic when we actually have a traversable navigable

Changes

Features

N/A

Bug Fixes

  • Returning 401 for stream-backed request bodies no longer throws fetch failed: expected non-null body source
  • Properly implements isTraversableNavigable() to avoid entering 401 retry block in Node.js

Breaking Changes

N/A

Testing

Added test cases for:

  1. GET requests with 401 responses (existing test)
  2. PUT requests with ReadableStream bodies receiving 401
  3. POST requests with JSON bodies receiving 401

All tests pass successfully.

@mcollina mcollina marked this pull request as draft March 29, 2026 10:09
@mcollina
Copy link
Copy Markdown
Member Author

Somehow I garbled the file, will fix

@mcollina mcollina force-pushed the fix/401-stream-compliant branch from a9eab22 to 30c8822 Compare March 29, 2026 10:24
- Properly implement isTraversableNavigable() to return false in Node.js
- Return 401 response directly for stream-backed bodies instead of throwing network error
- Aligns with Fetch spec discussion in whatwg/fetch#1132
- Add regression tests for PUT with ReadableStream and POST with JSON body
@mcollina mcollina force-pushed the fix/401-stream-compliant branch from 30c8822 to 7c1b59a Compare March 29, 2026 10:30
@mcollina mcollina marked this pull request as ready for review March 29, 2026 10:30
@mcollina
Copy link
Copy Markdown
Member Author

@KhafraDev this should be correct for us

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.

fetch() throws network error on POST 401 responses when body source is null Providing a request body when the response is 401 fails

2 participants