Skip to content

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

Closed
assimelha wants to merge 2 commits intocloudflare:mainfrom
assimelha:fix/miniflare-post-401-fetch-failed
Closed

fix(miniflare): prevent fetch failed on POST 401 responses#13328
assimelha wants to merge 2 commits intocloudflare:mainfrom
assimelha:fix/miniflare-post-401-fetch-failed

Conversation

@assimelha
Copy link
Copy Markdown

@assimelha assimelha commented Apr 7, 2026

Summary

POST requests returning HTTP 401 from Workers crash miniflare undici proxy with TypeError: fetch failed in dev mode. GET 401, POST 400, POST 403, and POST 500 all work fine.

Root cause

Undici Fetch spec implementation (step 14) has special handling for 401 responses: it tries to re-extract the request body source for credential re-authentication. But workerd uses streamed request bodies where body.source is null. This causes undici to throw:

Error: expected non-null body source

...which surfaces as TypeError: fetch failed at processResponse.

Fix

Set credentials: "omit" in miniflare fetch() wrapper when calling undici.fetch(). This bypasses the 401 authentication retry logic entirely. Miniflare dispatches to the workerd runtime directly — there is no HTTP authentication scheme to negotiate, so this code path should never trigger.

Testing

Verified locally by patching the compiled miniflare dist:

Request Before After
POST 200 OK OK
POST 400 OK OK
POST 401 fetch failed OK (returns JSON)
POST 403 OK OK
POST 500 OK OK
GET 401 OK OK

Impact

This affects any auth library (better-auth, lucia, etc.) running on Cloudflare Workers in dev mode where login with wrong credentials returns 401.

Fixes #13327


Open with Devin

When a Worker returns a 401 response to a POST request, undici Fetch
spec implementation (step 14) triggers HTTP authentication retry logic.
It attempts to re-extract the request body source for re-sending with
credentials, but workerd uses streamed request bodies where body.source
is null. This causes undici to throw a network error: "expected non-null
body source", surfacing as "TypeError: fetch failed".

Fix: Set credentials to "omit" in miniflare fetch wrapper when calling
undici.fetch(). This bypasses the 401 authentication retry entirely.
Miniflare dispatches to the workerd runtime directly - there is no HTTP
authentication scheme to negotiate, so this code path should never
trigger.

Verified: the fix resolves POST 401 crashes while preserving correct
behavior for POST 200, POST 400, POST 403, POST 500, and GET 401.

Fixes cloudflare#13327
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 7, 2026

🦋 Changeset detected

Latest commit: 39dedbb

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod workers-devprod requested review from a team and petebacondarwin and removed request for a team April 7, 2026 14:31
@workers-devprod
Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • packages/miniflare/src/http/fetch.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/index.ts: [@cloudflare/wrangler]

devin-ai-integration[bot]

This comment was marked as resolved.

@jamesopstad
Copy link
Copy Markdown
Contributor

/bonk review this PR

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review Summary

The PR correctly identifies the root cause of POST 401 failures in miniflare — undici's fetch spec implementation tries to re-extract the request body for credential re-authentication on 401 responses, but workerd's streamed bodies have body.source === null, causing a crash.

However, this bug is already fixed on main by the existing undici patch at patches/undici@7.24.4.patch, which sets isTraversableNavigable() to return false. This prevents the 401 credential-retry code path from ever executing, regardless of the credentials setting. The patch was introduced in commit 65e6684 (PR #13095) and references the same upstream issues (#12967, nodejs/undici#4910).

The credentials: "omit" changes in this PR would serve as defense-in-depth, which isn't a bad thing in principle. But there are a few concerns:

  1. Redundancy between the two locations. The credentials: "omit" set in index.ts flows through to fetch.ts via the RequestInit, and then fetch.ts also hardcodes credentials: "omit" in the undici.fetch() call. Either one alone would suffice — having both is confusing without an explanation of why both exist.

  2. The fetch.ts change affects all callers, not just dispatchFetch. The fetch() function from http/fetch.ts is also used from plugins/core/index.ts for fetchMock outbound services. Setting credentials: "omit" globally here changes behavior for all fetch calls going through miniflare's wrapper, not just the workerd dispatch path. While this is likely harmless (miniflare is not a browser and has no credential store), it deserves consideration.

  3. No tests. The PR has no test to verify the fix or prevent regression. A test in test/http/fetch.spec.ts that makes a POST request receiving a 401 response would be valuable, especially since this is a bug fix. The miniflare AGENTS.md notes test files use .spec.ts naming.

  4. The changeset description uses a conventional commit prefix (fix(miniflare): ...) which is acceptable per .changeset/README.md, but the body could be more concise.

I'd suggest either:

  • Option A: Close this PR, since the undici patch already resolves the issue.
  • Option B: If maintainers want defense-in-depth, pick only one of the two locations (I'd suggest fetch.ts since it covers all call sites), and add a test.

cc @cloudflare/wrangler for guidance on whether the defense-in-depth approach is desired given the existing patch.

// 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",
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.

// (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").


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.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 7, 2026

Review submitted successfully. Here's a summary of what I found:

Key finding: The bug this PR fixes (POST 401 crash in miniflare) is already resolved on main by an existing undici patch (patches/undici@7.24.4.patch) that sets isTraversableNavigable() to return false, preventing the 401 credential-retry code path from ever executing.

The PR's credentials: "omit" changes would add defense-in-depth, but I raised three inline concerns:

  1. Redundancy — The fix is applied in two locations (fetch.ts and index.ts), but either one alone suffices. Both together is confusing without explanation.
  2. Broader scope than described — The fetch.ts change affects all callers of the wrapper function, not just dispatchFetch.
  3. No tests — A regression test would be valuable for a bug fix like this.

I suggested the maintainers either close the PR (since the patch already fixes it) or, if they want defense-in-depth, keep only one location and add a test.

github run

@chshouyu
Copy link
Copy Markdown

chshouyu commented Apr 9, 2026

Has this issue been completely fixed?

@petebacondarwin
Copy link
Copy Markdown
Contributor

Thanks for offering this fix @assimelha.

There is a fix coming in Undici... nodejs/undici#4941.
We will upgrade to an appropriate version of that when it is available.

I am concerned that this PR is a workaround that might impact other aspects of how Miniflare forwards requests to Workers - adding userland headers to requests can break user's expectations.

@github-project-automation github-project-automation bot moved this from Untriaged to Done in workers-sdk Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Bug: POST requests returning 401 crash miniflare's undici proxy with 'fetch failed'

5 participants