From 80536b38bccc16057916ec32bea84de0e0ed4d3e Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:08:14 +0330 Subject: [PATCH 01/12] Extract formatInvalidRedirectsWarning helper for reuse at deploy time --- .../configuration/constructConfiguration.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/workers-shared/utils/configuration/constructConfiguration.ts b/packages/workers-shared/utils/configuration/constructConfiguration.ts index 6505e535bd..5aa677dc13 100644 --- a/packages/workers-shared/utils/configuration/constructConfiguration.ts +++ b/packages/workers-shared/utils/configuration/constructConfiguration.ts @@ -11,7 +11,38 @@ import type { MetadataRedirects, MetadataStaticRedirects, } from "../types"; -import type { Logger, ParsedHeaders, ParsedRedirects } from "./types"; +import type { + InvalidRedirectRule, + Logger, + ParsedHeaders, + ParsedRedirects, +} from "./types"; + +/** + * Build the human-readable warning text for a list of invalid `_redirects` rules. + * Shared between the dev-time construction path (`constructRedirects`) and any + * other caller (e.g. deploy) that wants to surface the same validation warnings + * without re-implementing the formatting. + */ +export function formatInvalidRedirectsWarning( + invalid: InvalidRedirectRule[], + relativePath: string +): string { + let invalidRedirectRulesList = ``; + + for (const { line, lineNumber, message } of invalid) { + invalidRedirectRulesList += `▶︎ ${message}\n`; + + if (line) { + invalidRedirectRulesList += ` at ${relativePath}${lineNumber ? `:${lineNumber}` : ""} | ${line}\n\n`; + } + } + + return ( + `Found ${invalid.length} invalid redirect rule${invalid.length === 1 ? "" : "s"}:\n` + + `${invalidRedirectRulesList}` + ); +} export function constructRedirects({ redirects, From 9cf08db9e14cdc3e2aa7fff9731406af1b902e94 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:08:24 +0330 Subject: [PATCH 02/12] Reuse formatInvalidRedirectsWarning in constructRedirects --- .../utils/configuration/constructConfiguration.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/workers-shared/utils/configuration/constructConfiguration.ts b/packages/workers-shared/utils/configuration/constructConfiguration.ts index 5aa677dc13..e7e7d5516b 100644 --- a/packages/workers-shared/utils/configuration/constructConfiguration.ts +++ b/packages/workers-shared/utils/configuration/constructConfiguration.ts @@ -71,19 +71,8 @@ export function constructRedirects({ ); if (num_invalid > 0) { - let invalidRedirectRulesList = ``; - - for (const { line, lineNumber, message } of redirects.invalid) { - invalidRedirectRulesList += `▶︎ ${message}\n`; - - if (line) { - invalidRedirectRulesList += ` at ${redirectsRelativePath}${lineNumber ? `:${lineNumber}` : ""} | ${line}\n\n`; - } - } - logger.warn( - `Found ${num_invalid} invalid redirect rule${num_invalid === 1 ? "" : "s"}:\n` + - `${invalidRedirectRulesList}` + formatInvalidRedirectsWarning(redirects.invalid, redirectsRelativePath) ); } From 8a3574858d9e6ea3bb2acd2e672fd1ac3787b146 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:08:52 +0330 Subject: [PATCH 03/12] Surface _redirects validation warnings at deploy time (fixes #14694, option 1) --- .../src/deploy/helpers/assets.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/deploy-helpers/src/deploy/helpers/assets.ts b/packages/deploy-helpers/src/deploy/helpers/assets.ts index 1a372246a4..c731248f95 100644 --- a/packages/deploy-helpers/src/deploy/helpers/assets.ts +++ b/packages/deploy-helpers/src/deploy/helpers/assets.ts @@ -2,6 +2,8 @@ import assert from "node:assert"; import { createReadStream } from "node:fs"; import { readdir, readFile, stat } from "node:fs/promises"; import * as path from "node:path"; +import { formatInvalidRedirectsWarning } from "@cloudflare/workers-shared/utils/configuration/constructConfiguration"; +import { parseRedirects } from "@cloudflare/workers-shared/utils/configuration/parseRedirects"; import { parseStaticRouting } from "@cloudflare/workers-shared/utils/configuration/parseStaticRouting"; import { CF_ASSETS_IGNORE_FILENAME, @@ -554,6 +556,26 @@ export function resolveAssetOptions( ? maybeGetFile(path.join(directory, HEADERS_FILENAME)) : undefined; + // The _redirects and _headers files are parsed in Miniflare in dev, and the + // asset worker is the source of truth for the parsed/enforced config at + // runtime, so we don't need the parsed result here for deploy. We do still + // parse `_redirects` for validation purposes so that authors get the same + // "invalid rule" / dynamic-rule-limit warnings at deploy time that they'd + // otherwise only see in `wrangler dev` (see issue #14694). This never + // changes what gets uploaded -- the raw file content is still uploaded as-is + // below, unchanged. + if (_redirects) { + const parsedRedirects = parseRedirects(_redirects); + if (parsedRedirects.invalid.length > 0) { + logger.warn( + formatInvalidRedirectsWarning( + parsedRedirects.invalid, + REDIRECTS_FILENAME + ) + ); + } + } + // defaults are set in asset worker const assetConfig: AssetConfig = { html_handling: config.assets?.html_handling, From 9208b0008cc38274c5146f9b237cb31d3b82c312 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:09:39 +0330 Subject: [PATCH 04/12] Add deploy-time tests for _redirects validation warning (#14694) --- .../src/__tests__/deploy/assets.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/wrangler/src/__tests__/deploy/assets.test.ts b/packages/wrangler/src/__tests__/deploy/assets.test.ts index 791a0fa647..bacab26314 100644 --- a/packages/wrangler/src/__tests__/deploy/assets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/assets.test.ts @@ -858,6 +858,41 @@ describe("deploy", () => { }, } `); + // A valid _redirects file should not produce any deploy-time warning. + expect(std.warn).toMatchInlineSnapshot(`""`); + }); + + it("should warn about invalid _redirects rules at deploy time without altering what is uploaded (#14694)", async ({ + expect, + }) => { + // A duplicate rule for the same path is invalid and should be flagged, + // but until now this warning only ever surfaced in `wrangler dev`. + const redirectsContent = "/foo /bar\n/foo /baz"; + const assets = [ + { filePath: "_redirects", content: redirectsContent }, + { filePath: "index.html", content: "" }, + ]; + writeAssets(assets); + writeWranglerConfig({ + assets: { directory: "assets" }, + }); + const bodies: AssetManifest[] = []; + await mockAUSRequest(bodies); + mockSubDomainRequest(); + mockUploadWorkerRequest({ + expectedAssets: { + jwt: "<>", + // The raw (unparsed, unmodified) _redirects file is still uploaded + // as-is -- the deploy-time validation is warning-only. + config: { + _redirects: redirectsContent, + }, + }, + expectedType: "none", + }); + await runWrangler("deploy"); + expect(std.warn).toContain("Found 1 invalid redirect rule"); + expect(std.warn).toContain("Ignoring duplicate rule for path /foo."); }); it("should resolve assets directory relative to cwd if using cli", async ({ From 48e158d2a5a91ee58b825aeddce7a117fa08416b Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:43:07 +0330 Subject: [PATCH 05/12] Add changeset for _redirects deploy-time warning (#14694) --- .changeset/warm-redirects-deploy-warning.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/warm-redirects-deploy-warning.md diff --git a/.changeset/warm-redirects-deploy-warning.md b/.changeset/warm-redirects-deploy-warning.md new file mode 100644 index 0000000000..0ce340cd8f --- /dev/null +++ b/.changeset/warm-redirects-deploy-warning.md @@ -0,0 +1,10 @@ +--- +"@cloudflare/workers-shared": patch +"@cloudflare/deploy-helpers": patch +--- + +Surface `_redirects` validation warnings at `wrangler deploy` time + +Previously, invalid `_redirects` rules (e.g. a duplicate rule for the same path, or a file that exceeds the 100-rule dynamic-rule budget and has its remaining lines silently dropped) were only reported as a warning in `wrangler dev`. `wrangler deploy` uploaded the raw `_redirects` file without any client-side validation, so the same issues went completely unreported at deploy time. + +`wrangler deploy` now parses `_redirects` for validation purposes and warns about any invalid rules, using the same messages already shown by `wrangler dev`. This does not change what gets uploaded — the raw file is still uploaded as-is, and the asset worker remains the authoritative parser at runtime. From 4423e21b3b0c8f47ed261d3564dab6815a3092a1 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Wed, 15 Jul 2026 22:49:14 +0330 Subject: [PATCH 06/12] Trigger CI on fork --- .changeset/warm-redirects-deploy-warning.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/warm-redirects-deploy-warning.md b/.changeset/warm-redirects-deploy-warning.md index 0ce340cd8f..ac9c23ebdf 100644 --- a/.changeset/warm-redirects-deploy-warning.md +++ b/.changeset/warm-redirects-deploy-warning.md @@ -8,3 +8,5 @@ Surface `_redirects` validation warnings at `wrangler deploy` time Previously, invalid `_redirects` rules (e.g. a duplicate rule for the same path, or a file that exceeds the 100-rule dynamic-rule budget and has its remaining lines silently dropped) were only reported as a warning in `wrangler dev`. `wrangler deploy` uploaded the raw `_redirects` file without any client-side validation, so the same issues went completely unreported at deploy time. `wrangler deploy` now parses `_redirects` for validation purposes and warns about any invalid rules, using the same messages already shown by `wrangler dev`. This does not change what gets uploaded — the raw file is still uploaded as-is, and the asset worker remains the authoritative parser at runtime. + + From 018173cf75788b962d79741925888988459d6d04 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Thu, 16 Jul 2026 00:26:15 +0330 Subject: [PATCH 07/12] Pass htmlHandling to parseRedirects in resolveAssetOptions (fixes cloudflare#14694 review comment) config.assets?.html_handling was already available in this function but wasn't threaded through to the new deploy-time parseRedirects() call, so htmlHandling defaulted to undefined -- which parseRedirects treats as "enabled" -- causing false "infinite loop detected" warnings for users who set html_handling: "none" on rules like `/ /index.html`. Now passes the same option the Vite plugin already passes correctly. --- packages/deploy-helpers/src/deploy/helpers/assets.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/deploy-helpers/src/deploy/helpers/assets.ts b/packages/deploy-helpers/src/deploy/helpers/assets.ts index c731248f95..d49c17a359 100644 --- a/packages/deploy-helpers/src/deploy/helpers/assets.ts +++ b/packages/deploy-helpers/src/deploy/helpers/assets.ts @@ -565,7 +565,9 @@ export function resolveAssetOptions( // changes what gets uploaded -- the raw file content is still uploaded as-is // below, unchanged. if (_redirects) { - const parsedRedirects = parseRedirects(_redirects); + const parsedRedirects = parseRedirects(_redirects, { + htmlHandling: config.assets?.html_handling, + }); if (parsedRedirects.invalid.length > 0) { logger.warn( formatInvalidRedirectsWarning( From 627a9d23f24c81a52a1e514291b2a6464c12fe4b Mon Sep 17 00:00:00 2001 From: dumbCodesOnly Date: Thu, 16 Jul 2026 00:26:55 +0330 Subject: [PATCH 08/12] Add regression tests: html_handling: "none" must not produce a false infinite-loop warning at deploy time Covers the review finding on #14709 -- parseRedirects() previously ran with htmlHandling always undefined at deploy time (treated as enabled), so a rule like `/ /index.html` was flagged as an infinite loop even for projects that explicitly disabled HTML handling. Adds a test confirming no warning is produced now that config.assets?.html_handling is threaded through, plus a sanity check that the real infinite-loop case is still caught when HTML handling is left at its default. --- .../src/__tests__/deploy/assets.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/wrangler/src/__tests__/deploy/assets.test.ts b/packages/wrangler/src/__tests__/deploy/assets.test.ts index bacab26314..fae193ae2d 100644 --- a/packages/wrangler/src/__tests__/deploy/assets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/assets.test.ts @@ -895,6 +895,71 @@ describe("deploy", () => { expect(std.warn).toContain("Ignoring duplicate rule for path /foo."); }); + it("should not warn about a false infinite-loop when html_handling is 'none' (#14709 review)", async ({ + expect, + }) => { + // `/ /index.html` looks like an infinite-loop redirect only when HTML + // handling is enabled. Projects that explicitly disable it (html_handling: + // "none") must not get a false warning at deploy time -- the deploy-time + // validation has to respect the same option the Vite plugin already passes. + const redirectsContent = "/ /index.html"; + const assets = [ + { filePath: "_redirects", content: redirectsContent }, + { filePath: "index.html", content: "" }, + ]; + writeAssets(assets); + writeWranglerConfig({ + assets: { directory: "assets", html_handling: "none" }, + }); + const bodies: AssetManifest[] = []; + await mockAUSRequest(bodies); + mockSubDomainRequest(); + mockUploadWorkerRequest({ + expectedAssets: { + jwt: "<>", + config: { + _redirects: redirectsContent, + html_handling: "none", + }, + }, + expectedType: "none", + }); + await runWrangler("deploy"); + expect(std.warn).not.toContain("Infinite loop detected"); + expect(std.warn).toMatchInlineSnapshot(`""`); + }); + + it("should still warn about a real infinite-loop when html_handling is enabled (default)", async ({ + expect, + }) => { + // Sanity check for the other side of the fix: when HTML handling is left + // at its default (enabled), the same rule genuinely is an infinite loop + // and should still be flagged. + const redirectsContent = "/ /index.html"; + const assets = [ + { filePath: "_redirects", content: redirectsContent }, + { filePath: "index.html", content: "" }, + ]; + writeAssets(assets); + writeWranglerConfig({ + assets: { directory: "assets" }, + }); + const bodies: AssetManifest[] = []; + await mockAUSRequest(bodies); + mockSubDomainRequest(); + mockUploadWorkerRequest({ + expectedAssets: { + jwt: "<>", + config: { + _redirects: redirectsContent, + }, + }, + expectedType: "none", + }); + await runWrangler("deploy"); + expect(std.warn).toContain("Infinite loop detected"); + }); + it("should resolve assets directory relative to cwd if using cli", async ({ expect, }) => { From 4ef9ffe2fb6259339d681ff44183780129f756c5 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly <225476909+dumbCodesOnly@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:48:00 +0330 Subject: [PATCH 09/12] Remove leftover fork-CI-trigger comment per NuroDev's review request on PR #14709. --- .changeset/warm-redirects-deploy-warning.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/warm-redirects-deploy-warning.md b/.changeset/warm-redirects-deploy-warning.md index ac9c23ebdf..5b130801a2 100644 --- a/.changeset/warm-redirects-deploy-warning.md +++ b/.changeset/warm-redirects-deploy-warning.md @@ -9,4 +9,3 @@ Previously, invalid `_redirects` rules (e.g. a duplicate rule for the same path, `wrangler deploy` now parses `_redirects` for validation purposes and warns about any invalid rules, using the same messages already shown by `wrangler dev`. This does not change what gets uploaded — the raw file is still uploaded as-is, and the asset worker remains the authoritative parser at runtime. - From 90ed70340a8fdb14f44d791fd03ed555fbc3141e Mon Sep 17 00:00:00 2001 From: dumbCodesOnly <225476909+dumbCodesOnly@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:48:06 +0330 Subject: [PATCH 10/12] Drop issue/review references from test names per NuroDev's review request on PR #14709. --- packages/wrangler/src/__tests__/deploy/assets.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wrangler/src/__tests__/deploy/assets.test.ts b/packages/wrangler/src/__tests__/deploy/assets.test.ts index fae193ae2d..195eb6f552 100644 --- a/packages/wrangler/src/__tests__/deploy/assets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/assets.test.ts @@ -862,7 +862,7 @@ describe("deploy", () => { expect(std.warn).toMatchInlineSnapshot(`""`); }); - it("should warn about invalid _redirects rules at deploy time without altering what is uploaded (#14694)", async ({ + it("should warn about invalid _redirects rules at deploy time without altering what is uploaded", async ({ expect, }) => { // A duplicate rule for the same path is invalid and should be flagged, @@ -895,7 +895,7 @@ describe("deploy", () => { expect(std.warn).toContain("Ignoring duplicate rule for path /foo."); }); - it("should not warn about a false infinite-loop when html_handling is 'none' (#14709 review)", async ({ + it("should not warn about a false infinite-loop when html_handling is 'none'", async ({ expect, }) => { // `/ /index.html` looks like an infinite-loop redirect only when HTML From 175cf65f69880202c18cfce3e35f25b2b705a0e4 Mon Sep 17 00:00:00 2001 From: dumbCodesOnly <225476909+dumbCodesOnly@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:21:28 +0330 Subject: [PATCH 11/12] Add wrangler to changeset front-matter so the deploy-time warning fix appears in the wrangler changelog, per Devin review feedback on PR #14709 --- .changeset/warm-redirects-deploy-warning.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/warm-redirects-deploy-warning.md b/.changeset/warm-redirects-deploy-warning.md index 5b130801a2..157e7cf0ca 100644 --- a/.changeset/warm-redirects-deploy-warning.md +++ b/.changeset/warm-redirects-deploy-warning.md @@ -1,4 +1,5 @@ --- +"wrangler": patch "@cloudflare/workers-shared": patch "@cloudflare/deploy-helpers": patch --- From 5f13481feb837bea418738e23bc05ce409b1db4b Mon Sep 17 00:00:00 2001 From: allocsys <225476909+allocsys@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:51:43 +0330 Subject: [PATCH 12/12] chore: fix oxfmt trailing-newline violation in changeset --- .changeset/warm-redirects-deploy-warning.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/warm-redirects-deploy-warning.md b/.changeset/warm-redirects-deploy-warning.md index 157e7cf0ca..023696fb1f 100644 --- a/.changeset/warm-redirects-deploy-warning.md +++ b/.changeset/warm-redirects-deploy-warning.md @@ -9,4 +9,3 @@ Surface `_redirects` validation warnings at `wrangler deploy` time Previously, invalid `_redirects` rules (e.g. a duplicate rule for the same path, or a file that exceeds the 100-rule dynamic-rule budget and has its remaining lines silently dropped) were only reported as a warning in `wrangler dev`. `wrangler deploy` uploaded the raw `_redirects` file without any client-side validation, so the same issues went completely unreported at deploy time. `wrangler deploy` now parses `_redirects` for validation purposes and warns about any invalid rules, using the same messages already shown by `wrangler dev`. This does not change what gets uploaded — the raw file is still uploaded as-is, and the asset worker remains the authoritative parser at runtime. -