Skip to content

[wrangler] fix: propagate zone from unstable_getMiniflareWorkerOptions so vite dev outbound CF-Worker reflects dev.host#13919

Open
petebacondarwin wants to merge 6 commits into
mainfrom
fix/13791-zone-on-unstable-get-miniflare-worker-options
Open

[wrangler] fix: propagate zone from unstable_getMiniflareWorkerOptions so vite dev outbound CF-Worker reflects dev.host#13919
petebacondarwin wants to merge 6 commits into
mainfrom
fix/13791-zone-on-unstable-get-miniflare-worker-options

Conversation

@petebacondarwin
Copy link
Copy Markdown
Contributor

@petebacondarwin petebacondarwin commented May 14, 2026

Fixes #13791.

Under vite dev and @cloudflare/vitest-pool-workers, outbound subrequests sent the CF-Worker header as <worker-name>.example.com even when dev.host or routes were configured. This broke local development against services that reject unknown CF-Worker hosts — the issue reporter hit it against Apple WeatherKit (403 Forbidden). wrangler dev --local was unaffected because it builds Miniflare worker options through a separate path that already sets zone.

The root cause was that unstable_getMiniflareWorkerOptions (and the equivalent getPlatformProxy worker-options path in packages/wrangler/src/api/integrations/platform/index.ts) never propagated zone, so Miniflare fell back to its ${workerName}.example.com default when constructing the CF-Worker header on outbound fetches.

The fix derives zone from the normalized config the same way wrangler dev does — prefer dev.host, otherwise fall back to the hostname of the first configured route via getHostFromRoute — and sets it on both worker options paths. @cloudflare/vite-plugin and @cloudflare/vitest-pool-workers pick this up automatically with no API changes.

Tests added at two layers:

  • A unit test (packages/wrangler/src/__tests__/unstable-get-miniflare-worker-options.test.ts) covering the derivation cases: dev.host set, single route string, routes array, unparseable */* falling back to zone_name, both set (dev.host wins), and neither set (undefined).
  • An end-to-end regression test in the existing cron-triggers playground that runs a side Node HTTP echo server, hits a new /cf-worker-header endpoint in the Worker, and asserts the outbound CF-Worker header matches the configured dev.host. Verified to fail with the exact pre-fix symptom (cron-trigger-worker.example.com) when the fix is reverted.

  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this restores parity with wrangler dev --local, which already matches the production behaviour that is documented elsewhere.

Open in Devin Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: 6bcea69

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

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

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

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk May 14, 2026
@workers-devprod workers-devprod requested review from a team and emily-shen and removed request for a team May 14, 2026 14:31
@workers-devprod
Copy link
Copy Markdown
Contributor

workers-devprod commented May 14, 2026

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 2026

✅ All changesets look good

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 14, 2026

Good — vitest-pool-workers consumes unstable_getMiniflareWorkerOptions and spreads workerOptions (which now includes zone), so it benefits from the fix without code changes. The changeset correctly includes it to document the behavioral change for its users.

I've completed a thorough review. Let me summarize my findings:

The fix is correct and well-tested. The getZoneFromConfig function properly mirrors the wrangler dev logic (prefer dev.host, fall back to getHostFromRoute(firstRoute)). It's applied at both call sites (getPlatformProxy and unstable_getMiniflareWorkerOptions). The unit tests cover all derivation cases. The E2E test in the playground is a solid regression test. The changeset correctly includes all three affected packages.

There are no logic bugs, security issues, backward compatibility violations, or incorrect API behavior.

LGTM

github run

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@13919

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@13919

miniflare

npm i https://pkg.pr.new/miniflare@13919

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@13919

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@13919

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@13919

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@13919

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@13919

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@13919

wrangler

npm i https://pkg.pr.new/wrangler@13919

commit: 6bcea69

Copy link
Copy Markdown
Contributor

@jamesopstad jamesopstad left a comment

Choose a reason for hiding this comment

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

We shouldn't be using the dev options from the Wrangler config in the Vite plugin. Can we reduce the scope of this PR so that it handles the inference from the routes without providing a way to override? We can then follow up with new options in the plugin config to handle the override.

@github-project-automation github-project-automation Bot moved this from Untriaged to In Review in workers-sdk May 14, 2026
@petebacondarwin petebacondarwin requested review from jamesopstad and removed request for emily-shen May 14, 2026 16:18
Copy link
Copy Markdown
Contributor

@jamesopstad jamesopstad left a comment

Choose a reason for hiding this comment

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

Looks good. Just a question over naming (zone vs hostname). The PR title and description also need updating.

Comment thread .changeset/fix-zone-on-unstable-get-miniflare-worker-options.md Outdated
describe("unstable_getMiniflareWorkerOptions", () => {
runInTempDir();

describe("zone derivation (used for the outbound CF-Worker header)", () => {
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.

Is zone the right word in this block or should it be hostname? Couldn't a single zone have multiple possible hostnames because of subdomains?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had to go and look up exactly what CF-Worker is supposed to contain in production...

From our docs...

It is set to the name of the zone which owns the Worker making the subrequest. For example, a Worker script on route for foo.example.com/* from example.com will have all subrequests with the header:

CF-Worker: example.com

The intended purpose of this header is to provide a means for recipients (for example, origins, load balancers, other Workers) to recognize, filter, and route traffic generated by Workers on specific zones.

It appears this is explicitly not the hostname of the request but the name of the zone. So I am now not sure if the logic for the zone guessing is quite right. I need to go and think about this a bit more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have reworked the logic for selecting the zone from the routes (and in wrangler dev the dev.host config).
As mentioned from the docs the zone is not always the hostname, so this approach fixes that (or at least doesn't make it any worse where it is ambiguous).

CF-Worker header behaviour matrix

Reference: Cloudflare HTTP headers — CF-Worker — production sets CF-Worker to the zone name that owns the Worker.
Legend

  • <wk> = <worker-name>.example.com (Miniflare's default when no zone is supplied)
  • ✅ matches production
  • ⚠️ best-effort (correct value requires an API lookup, so it's not knowable locally without one)
  • 🔧 changed by this PR
  • 💥 throws at startup (pre-existing behaviour from getInferredHost)
    Only the first configured route is consulted in both code paths.

wrangler dev --local

Wrangler config Before After Per docs
dev.host: "x.example.com" only x.example.com x.example.com n/a (dev.host is a local override)
route: "example.com/*" example.com example.com example.com
route: "foo.example.com/*" foo.example.com ⚠️ foo.example.com ⚠️ example.com
{ pattern: "foo.example.com/*", zone_name: "example.com" } foo.example.com example.com ✅ 🔧 example.com
{ pattern: "foo.example.com/*", zone_id: "abc" } foo.example.com ⚠️ foo.example.com ⚠️ example.com
{ pattern: "*/*", zone_name: "example.com" } example.com example.com example.com
{ pattern: "*/*", zone_id: "abc" } (no dev.host) 💥 💥 example.com
dev.host + { pattern: "foo.example.com/*", zone_name: "example.com" } dev.host example.com ✅ 🔧 example.com
dev.host + { pattern: "foo.example.com/*", zone_id: "abc" } dev.host dev.host ⚠️ example.com
nothing configured <wk> <wk> n/a

@cloudflare/vite-plugin · @cloudflare/vitest-pool-workers · getPlatformProxy

These paths intentionally ignore dev.host — the dev config block is specific to wrangler dev. Users who need a custom CF-Worker host in these environments should configure a route instead.

Wrangler config Before After Per docs
dev.host: "x.example.com" only <wk> <wk> n/a (dev.host not consulted)
route: "example.com/*" <wk> example.com ✅ 🔧 example.com
route: "foo.example.com/*" <wk> foo.example.com ⚠️ 🔧 example.com
{ pattern: "foo.example.com/*", zone_name: "example.com" } <wk> example.com ✅ 🔧 example.com
{ pattern: "foo.example.com/*", zone_id: "abc" } <wk> foo.example.com ⚠️ 🔧 example.com
{ pattern: "*/*", zone_name: "example.com" } <wk> example.com ✅ 🔧 example.com
{ pattern: "*/*", zone_id: "abc" } <wk> <wk> example.com
nothing configured <wk> <wk> n/a

Notes on the ⚠️ rows

When a route uses zone_id, a custom_domain, or a plain-string SimpleRoute on a subdomain (e.g. foo.example.com/*), determining the parent zone (example.com) requires resolving it server-side. We don't do that at dev time, so we fall back to the pattern's hostname as a best-effort approximation. To get the production-correct header locally, declare the zone explicitly via zone_name.

Comment thread packages/wrangler/src/api/integrations/platform/index.ts
Copy link
Copy Markdown
Contributor

@workers-devprod workers-devprod left a comment

Choose a reason for hiding this comment

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

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from In Review to Approved in workers-sdk May 15, 2026
@petebacondarwin petebacondarwin marked this pull request as draft May 15, 2026 16:39
petebacondarwin and others added 4 commits May 17, 2026 08:50
…ions` so `vite dev` outbound `CF-Worker` reflects `dev.host`

Under `vite dev` and `vitest-pool-workers`, outbound subrequests sent
the `CF-Worker` header as `<worker-name>.example.com` even when
`dev.host` or `routes` were configured, because the worker options
returned by `unstable_getMiniflareWorkerOptions` (and the equivalent
`getPlatformProxy` path) never set `zone`. Miniflare then fell back to
its default. `wrangler dev --local` was unaffected because it builds
worker options through a separate path that already sets `zone`.

Derive `zone` from the normalized config the same way `wrangler dev`
does — prefer `dev.host`, otherwise fall back to the hostname of the
first configured route via `getHostFromRoute`. Downstream consumers
pick this up automatically with no API changes.

Fixes #13791.
The dev config block is intended for wrangler dev. Drop the dev.host
branch from getZoneFromConfig so getPlatformProxy and
unstable_getMiniflareWorkerOptions only derive the outbound CF-Worker
zone from configured routes. Update tests, playground fixture, and the
changeset to match the reduced scope.
Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com>
…orker zone

Per https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-worker,
production sets the outbound `CF-Worker` header to the *zone name* that
owns the Worker — for a route on `foo.example.com/*` from zone
`example.com`, the header is `example.com`, not `foo.example.com`.

Previously, both `wrangler dev --local` and the new
`unstable_getMiniflareWorkerOptions` / `getPlatformProxy` paths derived
the zone via `getHostFromRoute`, which returns the pattern hostname.
That's correct when the pattern hostname is the zone apex, but wrong
when the user has explicitly configured a route with
`zone_name: "example.com"` for a subdomain pattern.

Introduce a shared `getZoneFromRoute` helper that prefers the route's
`zone_name` field when present and falls back to `getHostFromRoute`
otherwise (still a best-effort approximation when only `zone_id`, a
string pattern, or a `custom_domain` route is configured — resolving
the parent zone would require an API lookup). Use it in both the new
`getZoneFromConfig` (vite-plugin / vitest-pool-workers / getPlatformProxy)
and `wrangler dev`'s `resolveDev`, wiring the latter through a new
`StartDevWorkerOptions["dev"].zone` field so the controller no longer
aliases the CF-Worker zone to `origin.hostname` (which legitimately
remains the pattern hostname for `request.url` purposes).
@petebacondarwin petebacondarwin force-pushed the fix/13791-zone-on-unstable-get-miniflare-worker-options branch from b606848 to 1bfd0c4 Compare May 17, 2026 08:51
Compute the outbound `CF-Worker` zone inline in
`LocalRuntimeController.convertToConfigBundle` rather than threading a
new `dev.zone` field through `StartDevWorkerOptions`.

The rule: prefer the first route's explicit `zone_name` (the user's
unambiguous declaration of the parent zone, matching production), and
otherwise fall back to `dev.origin.hostname` (which already collapses
`dev.host` and the inferred-from-route-pattern fallback). This avoids
adding a new public-ish API surface while still applying the zone_name
fix to `wrangler dev --local` for consistency with the new
`unstable_getMiniflareWorkerOptions` / `getPlatformProxy` behaviour.
…comment to JSDoc

Adds a getZoneFromRoute test case covering when the route pattern can't
be parsed as a hostname AND there's no zone_name to fall back on — the
helper returns undefined, leaving Miniflare to apply its default
<worker-name>.example.com header.

Also moves the explanatory comment from the getZoneForCfWorkerHeader
call site into a JSDoc on the function itself.
@petebacondarwin petebacondarwin marked this pull request as ready for review May 17, 2026 20:06
@petebacondarwin
Copy link
Copy Markdown
Contributor Author

@jamesopstad - please can you take another look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

vite dev drops zone before Miniflare, causing CF-Worker to fall back to <worker>.example.com

3 participants