releases.shpreview

Workers

Bundle shrinks from 489 KB to 125 KB; externals now auto-derived
Patch Changes
  • #13933 90092c0 Thanks @petebacondarwin! - Derive bundler externals from package.json and shrink the published bundle

    The bundler's external list was previously hand-maintained and out of sync with package.jsonundici and semver were both listed as external despite being only devDependencies. The published dist/pool/index.mjs consequently contained a top-level import { fetch } from "undici" that was only resolvable because pnpm happened to hoist undici from other packages' devDependencies during local development.

    The bundler now derives its external list from dependencies + peerDependencies in package.json, making it impossible for a devDependency to silently end up externalized.

    Combined with the new "sideEffects": false declaration in @cloudflare/workers-utils, the unused cloudflared / tunnel exports (and their transitive undici import) are now tree-shaken out of the pool entirely. dist/pool/index.mjs no longer references undici at all, and shrinks from ~489 KB to ~125 KB.

  • Updated dependencies [52e9082, 0733688, fc1f7b9, 30657e1, 8c569c6, f598eac, 3a1fbed]:

    • wrangler@4.94.0
    • miniflare@4.20260521.0
Tunnel URLs now include scannable QR code
Minor Changes
  • #13989 f598eac Thanks @MattieTK! - Print a QR code alongside the tunnel URL when sharing via Cloudflare Tunnel

    When a tunnel is started (via wrangler dev --tunnel or the Vite plugin with tunnel: true), a scannable QR code is now printed to the terminal beneath the tunnel URL. This makes it easy to open the tunnel on a mobile device without manually copying the URL.

    The QR code uses Unicode block characters for a compact representation and is generated best-effort -- if generation fails for any reason, the tunnel URL is still displayed as before.

Patch Changes
Package now side-effect-free; undici moved to runtime dependencies
Patch Changes
  • #13933 90092c0 Thanks @petebacondarwin! - Mark @cloudflare/workers-utils as side-effect-free and properly declare undici as a runtime dependency

    The package now declares "sideEffects": false in its package.json so that downstream bundlers can tree-shake unused exports. In particular, consumers that only use a subset of the package (for example, getTodaysCompatDate from the main entry) will no longer carry the cloudflared / tunnel exports — or their transitive dependencies — in their final bundle.

    undici has been moved from devDependencies to dependencies. Previously it was incorrectly listed as a devDependency while the bundler config marked it as external, leaving the published dist/index.mjs with an unresolved import { fetch } from "undici" for anyone installing the package directly. undici is deliberately kept external (rather than bundled) so that downstream consumers don't end up with two copies of undici in their bundle — which would break instanceof Request/Response/Headers checks across the boundary and prevent setGlobalDispatcher / proxy configuration from applying to the bundled copy.

    vitest has been added as an optional peerDependency because the ./test-helpers sub-export uses vitest's vi, beforeEach, and afterEach APIs at runtime; consumers that import from ./test-helpers must have vitest installed themselves.

AI skills auto-install; tunnel URLs get QR codes
Minor Changes
  • #13897 52e9082 Thanks @dario-piotrowicz! - Add automatic Cloudflare skills installation for AI coding agents

    Wrangler now detects AI coding agents and offers to install Cloudflare skill files from the cloudflare/skills GitHub repository. Users are prompted once interactively; subsequent runs skip the prompt. Use --install-skills to install without prompting.

  • #13989 f598eac Thanks @MattieTK! - Print a QR code alongside the tunnel URL when sharing via Cloudflare Tunnel

    When a tunnel is started (via wrangler dev --tunnel or the Vite plugin with tunnel: true), a scannable QR code is now printed to the terminal beneath the tunnel URL. This makes it easy to open the tunnel on a mobile device without manually copying the URL.

    The QR code uses Unicode block characters for a compact representation and is generated best-effort -- if generation fails for any reason, the tunnel URL is still displayed as before.

  • #13467 3a1fbed Thanks @deloreyj! - Add schedule property to Workflow bindings for cron-based triggering

    Note: This is a configuration-only change. Scheduled triggering of Workflow instances is not yet available — adding schedule to a Workflow binding will not result in scheduled invocations at this time. This change lays the groundwork for an upcoming feature.

    Workflow bindings in wrangler.json now accept an optional schedule field that configures one or more cron expressions to automatically trigger new workflow instances on a schedule.

    // wrangler.json
    {
      "workflows": [
        {
          "binding": "MY_WORKFLOW",
          "name": "my-workflow",
          "class_name": "MyWorkflow",
          "schedule": "0 9 * * 1"
        }
      ]
    }

    Multiple schedules can be provided as an array:

    {
      "workflows": [
        {
          "binding": "MY_WORKFLOW",
          "name": "my-workflow",
          "class_name": "MyWorkflow",
          "schedule": ["0 9 * * 1", "0 17 * * 5"]
        }
      ]
    }

    The schedule is sent to the Workflows control plane on wrangler deploy. Configuring schedule on a workflow binding that references an external script_name is an error — the schedule must be configured on the worker that defines the workflow.

Patch Changes
  • #13993 0733688 Thanks @dependabot! - Update dependencies of "miniflare", "wrangler"

    The following dependency versions have been updated:

    DependencyFromTo
    workerd1.20260520.11.20260521.1
  • #14008 fc1f7b9 Thanks @petebacondarwin! - Fix Access Service Token authentication for applications that only allow service tokens

    When using remote bindings against a Worker behind a Cloudflare Access application configured to only allow Service Auth tokens (no interactive user authentication), Wrangler previously ignored the CLOUDFLARE_ACCESS_CLIENT_ID and CLOUDFLARE_ACCESS_CLIENT_SECRET environment variables and the request would fail with a 403.

    This happened because Wrangler detects Access by looking for a 302 redirect to cloudflareaccess.com. A service-auth-only Access application has no interactive login path, so it responds with a hard 403 instead of redirecting. Wrangler concluded the domain was not behind Access and skipped attaching the service token headers entirely.

    The env-var check now runs before the Access detection step, so the configured service token credentials are always used when present.

  • #12277 8c569c6 Thanks @penalosa! - Include column names in D1 SQL export INSERT statements

    D1 SQL exports now include column names in INSERT statements (e.g., INSERT INTO "table" ("col1","col2") VALUES(...)). This ensures that exported SQL can be successfully imported even when the target table has columns in a different order than the original, which commonly occurs during iterative development when schemas evolve.

  • Updated dependencies [0733688, 30657e1]:

    • miniflare@4.20260521.0
TCP requests work with outboundService configured
Patch Changes
  • #13993 0733688 Thanks @dependabot! - Update dependencies of "miniflare", "wrangler"

    The following dependency versions have been updated:

    DependencyFromTo
    workerd1.20260520.11.20260521.1
  • #13999 30657e1 Thanks @edmundhung! - Fix TCP requests failing when outboundService is configured

    Workers using outboundService can now open TCP connections with cloudflare:sockets. Previously, TCP requests could throw an error when a custom outbound service was configured.

Sibling container tags no longer dropped during dev cleanup
Patch Changes
  • #13963 adc9221 Thanks @gabivlj! - Preserve sibling container image tags during local dev cleanup

    Wrangler now keeps other cloudflare-dev image tags from the same dev session when multiple containers share a Dockerfile. Previously, duplicate-image cleanup could remove earlier container tags if Docker BuildKit produced the same image ID for each build.

CF-Worker header now reflects zone name in local dev
Patch Changes
  • #13919 c7eab7f Thanks @petebacondarwin! - Fix the outbound CF-Worker header reflecting the route pattern hostname instead of the parent zone, and falling back to <worker-name>.example.com under vite dev, vitest-pool-workers, and getPlatformProxy

    Two related issues affected the CF-Worker header on outbound subrequests in local development:

    1. Under @cloudflare/vite-plugin, @cloudflare/vitest-pool-workers, and getPlatformProxy, the header fell back to <worker-name>.example.com even when routes were configured, because unstable_getMiniflareWorkerOptions and the equivalent getPlatformProxy worker-options path did not propagate a zone value to Miniflare. This broke local development against services that reject unknown CF-Worker hosts (for example, Apple WeatherKit returns 403 Forbidden).
    2. Across the above paths and wrangler dev --local, when a route used the zone_name field (for example { pattern: "foo.example.com/*", zone_name: "example.com" }), the header was set to the pattern's hostname (foo.example.com) rather than the zone name (example.com). Production sets CF-Worker to the zone name that owns the Worker, so this was inconsistent with deployed behaviour.

    Both bugs are fixed: the new unstable_getMiniflareWorkerOptions / getPlatformProxy path now propagates a zone derived from the first configured route, and all four local-dev paths now prefer a route's explicit zone_name over the pattern hostname when computing that zone. When zone_name isn't set, the existing best-effort behaviour is preserved — for wrangler dev this means dev.host is still honoured as a local override and the pattern hostname is used as a final fallback. Resolving the parent zone for zone_id-only, custom_domain, or plain-string routes would require an API lookup, so locally we still approximate it with the pattern hostname.

    Note: dev.host is intentionally not consulted by the unstable_getMiniflareWorkerOptions / getPlatformProxy paths — the dev config block is specific to wrangler dev.

  • Updated dependencies [fa1f61f, 2679e05, 7e40d98, adc9221, 735852d, d803737, c7eab7f, e04e180, 59cd880, 62abf97, e8c2031, e349fe0, da0fa8c, a5c9365]:

    • miniflare@4.20260520.0
    • wrangler@4.93.1
WebSocket memory disclosure patched; host validation tightened
Patch Changes
  • #13978 fa1f61f Thanks @sassyconsultingllc! - Bump ws from 8.18.0 to 8.20.1 to address GHSA-58qx-3vcg-4xpx

    GHSA-58qx-3vcg-4xpx / CVE-2026-45736 reports an uninitialized-memory disclosure in ws@<8.20.1 when a TypedArray is passed as the reason argument to WebSocket.close(). The fix shipped in ws@8.20.1 on 2026-05-12. This change bumps the workspace catalog entry so that miniflare, wrangler, and @cloudflare/vite-plugin all pick up the patched release.

  • #13977 2679e05 Thanks @dependabot! - Update dependencies of "miniflare", "wrangler"

    The following dependency versions have been updated:

    DependencyFromTo
    workerd1.20260518.11.20260519.1
  • #13984 7e40d98 Thanks @dependabot! - Update dependencies of "miniflare", "wrangler"

    The following dependency versions have been updated:

    DependencyFromTo
    workerd1.20260519.11.20260520.1
  • #13912 d803737 Thanks @petebacondarwin! - Fix /cdn-cgi/* host validation incorrectly accepting subdomains of exact configured routes

    Miniflare's /cdn-cgi/* host/origin validator was treating exact configured routes the same as wildcard configured routes, so a request whose Host or Origin hostname was a subdomain of an exact route (e.g. sub.my-custom-site.com for a my-custom-site.com/* route) was incorrectly accepted. Exact configured routes and the configured upstream hostname are now required to match the request hostname exactly. Subdomain matching is only applied to wildcard routes such as *.example.com/*. Localhost hostnames continue to be allowed as before.

    This affects wrangler dev and local development through @cloudflare/vite-plugin, both of which use Miniflare under the hood.

  • #13971 59cd880 Thanks @petebacondarwin! - Improve error diagnostics in the Browser Run binding worker

    When the local Browser Run binding failed to reach an upstream — for example when Chrome failed to launch and miniflare's loopback /browser/launch endpoint returned a 500 with a stack-trace text body — the binding worker would call response.json() on the non-JSON body and throw an opaque SyntaxError: Unexpected token X, "..." is not valid JSON. The actual upstream error message (e.g. Chrome readiness probe at ... timed out after 5000ms) was discarded.

    The binding worker now reads the response body as text first, surfaces the HTTP status and body content in the thrown error, and chains the original SyntaxError via cause when the body was a 2xx response that didn't parse as JSON. This makes both local-dev failures and CI test flakes self-diagnosing.

  • #13980 e8c2031 Thanks @petebacondarwin! - Recover from corrupted @puppeteer/browsers cache when launching a Browser Run session

    When Miniflare's local Browser Run binding launches Chrome, it calls @puppeteer/browsers' install() to ensure the binary is present. If a previous install() was interrupted mid-extraction (test timeout, process kill, antivirus quarantine), the cache directory can be left partially populated — the folder exists but the executable inside it is missing. install() then throws The browser folder (...) exists but the executable (...) is missing on every subsequent call within the same process and the entire test session, breaking every later Browser Run operation until the cache is manually cleared.

    launchBrowser now catches that specific error, removes the corrupted cache directory, and retries install() once. If the corruption persists after cleanup, the original error is rethrown with a clearer message.

    This complements #13971, which surfaced the original error from inside the binding worker. With that diagnostic in place and this self-healing layer, the previously-intermittent "browser folder exists but executable missing" failure mode should no longer fail an entire CI run.

WebSocket memory leak patched; host validation fixed
Patch Changes
  • #13978 fa1f61f Thanks @sassyconsultingllc! - Bump ws from 8.18.0 to 8.20.1 to address GHSA-58qx-3vcg-4xpx

    GHSA-58qx-3vcg-4xpx / CVE-2026-45736 reports an uninitialized-memory disclosure in ws@<8.20.1 when a TypedArray is passed as the reason argument to WebSocket.close(). The fix shipped in ws@8.20.1 on 2026-05-12. This change bumps the workspace catalog entry so that miniflare, wrangler, and @cloudflare/vite-plugin all pick up the patched release.

  • #13912 d803737 Thanks @petebacondarwin! - Fix /cdn-cgi/* host validation incorrectly accepting subdomains of exact configured routes

    Miniflare's /cdn-cgi/* host/origin validator was treating exact configured routes the same as wildcard configured routes, so a request whose Host or Origin hostname was a subdomain of an exact route (e.g. sub.my-custom-site.com for a my-custom-site.com/* route) was incorrectly accepted. Exact configured routes and the configured upstream hostname are now required to match the request hostname exactly. Subdomain matching is only applied to wildcard routes such as *.example.com/*. Localhost hostnames continue to be allowed as before.

    This affects wrangler dev and local development through @cloudflare/vite-plugin, both of which use Miniflare under the hood.

  • #13919 c7eab7f Thanks @petebacondarwin! - Fix the outbound CF-Worker header reflecting the route pattern hostname instead of the parent zone, and falling back to <worker-name>.example.com under vite dev, vitest-pool-workers, and getPlatformProxy

    Two related issues affected the CF-Worker header on outbound subrequests in local development:

    1. Under @cloudflare/vite-plugin, @cloudflare/vitest-pool-workers, and getPlatformProxy, the header fell back to <worker-name>.example.com even when routes were configured, because unstable_getMiniflareWorkerOptions and the equivalent getPlatformProxy worker-options path did not propagate a zone value to Miniflare. This broke local development against services that reject unknown CF-Worker hosts (for example, Apple WeatherKit returns 403 Forbidden).
    2. Across the above paths and wrangler dev --local, when a route used the zone_name field (for example { pattern: "foo.example.com/*", zone_name: "example.com" }), the header was set to the pattern's hostname (foo.example.com) rather than the zone name (example.com). Production sets CF-Worker to the zone name that owns the Worker, so this was inconsistent with deployed behaviour.

    Both bugs are fixed: the new unstable_getMiniflareWorkerOptions / getPlatformProxy path now propagates a zone derived from the first configured route, and all four local-dev paths now prefer a route's explicit zone_name over the pattern hostname when computing that zone. When zone_name isn't set, the existing best-effort behaviour is preserved — for wrangler dev this means dev.host is still honoured as a local override and the pattern hostname is used as a final fallback. Resolving the parent zone for zone_id-only, custom_domain, or plain-string routes would require an API lookup, so locally we still approximate it with the pattern hostname.

    Note: dev.host is intentionally not consulted by the unstable_getMiniflareWorkerOptions / getPlatformProxy paths — the dev config block is specific to wrangler dev.

  • Updated dependencies [fa1f61f, 2679e05, 7e40d98, adc9221, 735852d, d803737, c7eab7f, e04e180, 59cd880, 62abf97, e8c2031, e349fe0, da0fa8c, a5c9365]:

    • miniflare@4.20260520.0
    • wrangler@4.93.1
Workflow steps now support rollback callbacks
Minor Changes
  • #13983 9803586 Thanks @vaishnav-mk! - Add rollback support for local Workflows development

    Workflow steps can now register a compensation callback with trailing rollback options: step.do(name, fn, { rollback }) and step.do(name, config, fn, { rollback, rollbackConfig }). When the workflow fails, the local engine runs every registered rollback in reverse step-start order (LIFO), giving steps the opportunity to undo their side effects.

    Each rollback executes through an internal rollback-scoped Context.do, so it inherits the existing retry / timeout / attempt-tracking machinery. rollbackConfig lets users override the per-rollback config.

    Note: the public rollback option type lands with workerd's workflows_step_rollback compat flag. Until that ships, the trailing rollback options only flow through when called through the StepPromise wrapper from a worker that has the flag enabled.