Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-miniflare-post-401.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

fix(miniflare): prevent fetch failed on POST 401 responses

Set `credentials: "omit"` in miniflare's fetch wrapper to bypass undici's HTTP 401 authentication retry logic. Workerd uses streamed request bodies where `body.source` is null, causing undici to throw "expected non-null body source" on POST 401 responses.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: per .changeset/README.md, the body should explain "the reason for the change and anything notable about the approach" in a way useful for changelog readers. Consider simplifying to focus on user-facing impact:

Suggested change
Set `credentials: "omit"` in miniflare's fetch wrapper to bypass undici's HTTP 401 authentication retry logic. Workerd uses streamed request bodies where `body.source` is null, causing undici to throw "expected non-null body source" on POST 401 responses.
Set `credentials: "omit"` in miniflare's fetch wrapper to prevent POST requests returning HTTP 401 from crashing with `TypeError: fetch failed` during local development.

9 changes: 9 additions & 0 deletions packages/miniflare/src/http/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ export async function fetch(

const response = await undici.fetch(request, {
dispatcher: requestInit?.dispatcher,
// Prevent undici's HTTP 401 authentication retry logic from triggering.
// When a POST request with a streamed body (body.source === null) receives
// a 401, undici tries to re-extract the request body for re-sending with
// credentials, but fails with "expected non-null body source" because
// workerd uses streamed bodies. Setting credentials to "omit" bypasses
// the 401 handling entirely (Fetch spec §4.4 step 14). This is safe
// because miniflare dispatches to workerd directly — there is no HTTP
// authentication to retry.
credentials: "omit",
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes credentials: "omit" apply to all callers of this fetch() wrapper — not just dispatchFetch(). For instance, plugins/core/index.ts uses it for fetchMock outbound services:

value.outboundService = (req) => fetch(req, { dispatcher: fetchMock });

While likely harmless (miniflare has no credential store), this is a broader behavioral change than the PR description suggests. Worth noting explicitly.

Also, this fix is redundant with the existing undici patch at patches/undici@7.24.4.patch which already sets isTraversableNavigable() to return false, preventing the 401 credential retry code path from ever executing.

});
return new Response(response.body, response);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2707,6 +2707,14 @@ export class Miniflare {

const forwardInit = forward as RequestInit;
forwardInit.dispatcher = dispatcher;
// Omit credentials to prevent undici's HTTP 401 authentication retry
// logic from triggering. When a POST request with a streamed body
// receives a 401, undici tries to re-extract the body source for
// credential re-authentication. Since workerd uses streamed bodies
// (body.source === null), this causes a "fetch failed" network error
// with "expected non-null body source". Setting credentials to "omit"
// bypasses the 401 handling entirely (see Fetch spec step 14).
forwardInit.credentials = "omit";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the credentials: "omit" added in fetch.ts (line 94). The flow is:

  1. dispatchFetch sets forwardInit.credentials = "omit" here
  2. fetch() in fetch.ts creates new Request(input, requestInit) — undici's constructor preserves credentials: "omit" from init
  3. undici.fetch(request, { dispatcher, credentials: "omit" }) creates a new internal Request, overriding credentials again

Both set credentials: "omit" on the final internal request that undici's fetch algorithm reads, so either one alone suffices. If both are kept, a comment should explain why (e.g., "also set here in case fetch.ts wrapper is bypassed in the future").

const response = await fetch(url, forwardInit);

// If the Worker threw an uncaught exception, propagate it to the caller
Expand Down
Loading