From 546f07d001c55f55d8793b2d38514ad2675247ec Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Tue, 16 Jun 2026 10:54:30 +0530 Subject: [PATCH 1/5] fix(wrangler): don't crash when source-mapping truncated error chunks Wraps the prepareStack call in getSourceMappedString in a try-catch so that if the source map library throws (e.g. because a truncated stderr chunk produced a call site with an invalid column number), the original un-source-mapped string is returned instead of crashing wrangler dev. --- .../fix-sourcemap-crash-invalid-column.md | 7 +++ .../src/deploy/helpers/sourcemap.ts | 45 +++++++++++-------- 2 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 .changeset/fix-sourcemap-crash-invalid-column.md diff --git a/.changeset/fix-sourcemap-crash-invalid-column.md b/.changeset/fix-sourcemap-crash-invalid-column.md new file mode 100644 index 0000000000..0820aa616d --- /dev/null +++ b/.changeset/fix-sourcemap-crash-invalid-column.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Prevent `wrangler dev` crash when source-mapping a truncated error chunk + +When a worker logs many errors in quick succession, the stderr chunks received by `wrangler dev` can be truncated mid-stack-frame, leaving a call site with an invalid column number. The source map library throws in that case, which was crashing the wrangler process entirely. The error is now caught and the original (un-source-mapped) text is returned instead. diff --git a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts index 8aa08433f2..c7afbf4cc1 100644 --- a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts +++ b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts @@ -138,27 +138,34 @@ export function getSourceMappedString( const callSiteLines = Array.from(value.matchAll(CALL_SITE_REGEXP)); const callSites = callSiteLines.map(lineMatchToCallSite); const prepareStack = getSourceMappingPrepareStackTrace(retrieveSourceMap); - const sourceMappedStackTrace: string = prepareStack( - placeholderError, - callSites - ); - const sourceMappedCallSiteLines = sourceMappedStackTrace.split("\n").slice(1); + try { + const sourceMappedStackTrace: string = prepareStack( + placeholderError, + callSites + ); + const sourceMappedCallSiteLines = sourceMappedStackTrace + .split("\n") + .slice(1); - for (let i = 0; i < callSiteLines.length; i++) { - // If a call site doesn't have a file name, it's likely invalid, so don't - // apply source mapping (see cloudflare/workers-sdk#4668) - if (callSites[i].getFileName() === undefined) { - continue; - } + for (let i = 0; i < callSiteLines.length; i++) { + // If a call site doesn't have a file name, it's likely invalid, so don't + // apply source mapping (see cloudflare/workers-sdk#4668) + if (callSites[i].getFileName() === undefined) { + continue; + } - const callSiteLine = callSiteLines[i][0]; - const callSiteAtIndex = callSiteLine.indexOf("at"); - assert(callSiteAtIndex !== -1); // Matched against `CALL_SITE_REGEXP` - const callSiteLineLeftPad = callSiteLine.substring(0, callSiteAtIndex); - value = value.replace( - callSiteLine, - callSiteLineLeftPad + sourceMappedCallSiteLines[i].trimStart() - ); + const callSiteLine = callSiteLines[i][0]; + const callSiteAtIndex = callSiteLine.indexOf("at"); + assert(callSiteAtIndex !== -1); // Matched against `CALL_SITE_REGEXP` + const callSiteLineLeftPad = callSiteLine.substring(0, callSiteAtIndex); + value = value.replace( + callSiteLine, + callSiteLineLeftPad + sourceMappedCallSiteLines[i].trimStart() + ); + } + } catch { + // Source map processing failed (e.g. truncated stderr chunk with an invalid + // column number). Return the original string unchanged. } return value; } From 3df817a88075297d798c328416d1d5820f23c1a2 Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Wed, 17 Jun 2026 10:31:04 +0530 Subject: [PATCH 2/5] fix(deploy-helpers): narrow sourcemap try-catch to prepareStack only and fix null check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Narrow try-catch to wrap only `prepareStack()` and early-return on failure, so the for-loop replacements are never partially applied then silently dropped - Fix `getFileName() === undefined` → `=== null` (`CallSite.getFileName()` returns `string | null`, never `undefined`, so the old guard always evaluated false) - Add regression test verifying `getSourceMappedString` returns the original value when source mapping fails instead of throwing --- .../src/deploy/helpers/sourcemap.ts | 47 +++++++++---------- .../deploy-helpers/tests/sourcemap.test.ts | 14 ++++++ 2 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 packages/deploy-helpers/tests/sourcemap.test.ts diff --git a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts index c7afbf4cc1..83e075dfad 100644 --- a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts +++ b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts @@ -138,34 +138,31 @@ export function getSourceMappedString( const callSiteLines = Array.from(value.matchAll(CALL_SITE_REGEXP)); const callSites = callSiteLines.map(lineMatchToCallSite); const prepareStack = getSourceMappingPrepareStackTrace(retrieveSourceMap); + let sourceMappedStackTrace: string; try { - const sourceMappedStackTrace: string = prepareStack( - placeholderError, - callSites - ); - const sourceMappedCallSiteLines = sourceMappedStackTrace - .split("\n") - .slice(1); - - for (let i = 0; i < callSiteLines.length; i++) { - // If a call site doesn't have a file name, it's likely invalid, so don't - // apply source mapping (see cloudflare/workers-sdk#4668) - if (callSites[i].getFileName() === undefined) { - continue; - } + sourceMappedStackTrace = prepareStack(placeholderError, callSites); + } catch { + // prepareStack threw (e.g. truncated stderr chunk with an invalid column + // number). Return the original string unchanged. + return value; + } + const sourceMappedCallSiteLines = sourceMappedStackTrace.split("\n").slice(1); - const callSiteLine = callSiteLines[i][0]; - const callSiteAtIndex = callSiteLine.indexOf("at"); - assert(callSiteAtIndex !== -1); // Matched against `CALL_SITE_REGEXP` - const callSiteLineLeftPad = callSiteLine.substring(0, callSiteAtIndex); - value = value.replace( - callSiteLine, - callSiteLineLeftPad + sourceMappedCallSiteLines[i].trimStart() - ); + for (let i = 0; i < callSiteLines.length; i++) { + // If a call site doesn't have a file name, it's likely invalid, so don't + // apply source mapping (see cloudflare/workers-sdk#4668) + if (callSites[i].getFileName() === null) { + continue; } - } catch { - // Source map processing failed (e.g. truncated stderr chunk with an invalid - // column number). Return the original string unchanged. + + const callSiteLine = callSiteLines[i][0]; + const callSiteAtIndex = callSiteLine.indexOf("at"); + assert(callSiteAtIndex !== -1); // Matched against `CALL_SITE_REGEXP` + const callSiteLineLeftPad = callSiteLine.substring(0, callSiteAtIndex); + value = value.replace( + callSiteLine, + callSiteLineLeftPad + sourceMappedCallSiteLines[i].trimStart() + ); } return value; } diff --git a/packages/deploy-helpers/tests/sourcemap.test.ts b/packages/deploy-helpers/tests/sourcemap.test.ts new file mode 100644 index 0000000000..b4fb831aa8 --- /dev/null +++ b/packages/deploy-helpers/tests/sourcemap.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from "vitest"; +import { getSourceMappedString } from "../src/deploy/helpers/sourcemap"; + +describe("getSourceMappedString", () => { + it("returns original value when source mapping throws", ({ expect }) => { + const value = `Error: test\n at Object. (/some/file.js:1:1)`; + + const result = getSourceMappedString(value, () => { + throw new Error("simulated source map failure"); + }); + + expect(result).toBe(value); + }); +}); From f179b04d77edb9f13a2c3965cd7a438b4205685d Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Wed, 17 Jun 2026 11:23:08 +0530 Subject: [PATCH 3/5] refactor(deploy-helpers): extract tryPrepareStack helper to avoid mutable let Factors the try-catch into a dedicated `tryPrepareStack` helper that returns `null` on failure instead of throwing, letting `getSourceMappedString` use `const` for the result and check `=== null` before entering the for-loop. Addresses Copilot review feedback (prefer const over let-bridging a try block) and aligns with petebacondarwin's suggestion to make prepareStack non-throwing. --- .../src/deploy/helpers/sourcemap.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts index 83e075dfad..825298be94 100644 --- a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts +++ b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts @@ -122,6 +122,24 @@ function callFrameToCallSite(frame: Protocol.Runtime.CallFrame): CallSite { }); } +/** + * Calls `prepareStack` and returns `null` if it throws (e.g. when a truncated + * stderr chunk produces an invalid column number). Returning `null` lets + * `getSourceMappedString` fall back to the original string without executing + * the replacement loop against a partially-computed result. + */ +function tryPrepareStack( + prepareStack: ReturnType, + error: Error, + callSites: NodeJS.CallSite[] +): string | null { + try { + return prepareStack(error, callSites); + } catch { + return null; + } +} + const placeholderError = new Error(); export function getSourceMappedString( value: string, @@ -138,12 +156,12 @@ export function getSourceMappedString( const callSiteLines = Array.from(value.matchAll(CALL_SITE_REGEXP)); const callSites = callSiteLines.map(lineMatchToCallSite); const prepareStack = getSourceMappingPrepareStackTrace(retrieveSourceMap); - let sourceMappedStackTrace: string; - try { - sourceMappedStackTrace = prepareStack(placeholderError, callSites); - } catch { - // prepareStack threw (e.g. truncated stderr chunk with an invalid column - // number). Return the original string unchanged. + const sourceMappedStackTrace = tryPrepareStack( + prepareStack, + placeholderError, + callSites + ); + if (sourceMappedStackTrace === null) { return value; } const sourceMappedCallSiteLines = sourceMappedStackTrace.split("\n").slice(1); From e3867874f363178959fdacaa510cf789c2f07510 Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Thu, 18 Jun 2026 11:00:23 +0530 Subject: [PATCH 4/5] fix: log debug message when source map application fails in tryPrepareStack The catch block was silently swallowing exceptions, making it hard to diagnose unexpected source-map regressions. Now logs at debug level before returning null so the fallback is still transparent to users but visible with --log-level=debug. --- packages/deploy-helpers/src/deploy/helpers/sourcemap.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts index 825298be94..f6094fc6ec 100644 --- a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts +++ b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts @@ -2,6 +2,7 @@ import assert from "node:assert"; import url from "node:url"; import { maybeGetFile } from "@cloudflare/workers-shared"; import { getFreshSourceMapSupport } from "miniflare"; +import { logger } from "../../shared/context"; import type { Options } from "@cspotcode/source-map-support"; import type Protocol from "devtools-protocol"; @@ -135,7 +136,10 @@ function tryPrepareStack( ): string | null { try { return prepareStack(error, callSites); - } catch { + } catch (err) { + logger.debug( + `Source map application failed, falling back to original stack trace: ${err}` + ); return null; } } From eee98973a726965498bc4435a7b219a53279c07b Mon Sep 17 00:00:00 2001 From: Matin Gathani Date: Thu, 18 Jun 2026 11:17:16 +0530 Subject: [PATCH 5/5] fix(deploy-helpers): guard logger.debug with optional chaining in tryPrepareStack logger from shared/context is undefined until initDeployHelpersContext() is called. Tests that call getSourceMappedString directly never initialize the context, so logger?.debug avoids a TypeError when the logger has not been set up. --- packages/deploy-helpers/src/deploy/helpers/sourcemap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts index f6094fc6ec..1ca2579300 100644 --- a/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts +++ b/packages/deploy-helpers/src/deploy/helpers/sourcemap.ts @@ -137,7 +137,7 @@ function tryPrepareStack( try { return prepareStack(error, callSites); } catch (err) { - logger.debug( + logger?.debug( `Source map application failed, falling back to original stack trace: ${err}` ); return null;