Skip to content

Commit e4b2707

Browse files
committed
fix(core): preserve undefined for optional INTERNAL_ERROR fields
truncateMessage(undefined) and truncateStack(undefined) returned empty strings, breaking the `error.message ?? fallback` nullish coalescing in createErrorTaskError. INTERNAL_ERROR.message and stackTrace are optional in the schema, so sanitizeError now preserves undefined for them.
1 parent d05f017 commit e4b2707

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

packages/core/src/v3/errors.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,20 @@ export function sanitizeError(error: TaskRunError): TaskRunError {
332332
};
333333
}
334334
case "INTERNAL_ERROR": {
335+
// message and stackTrace are optional for INTERNAL_ERROR — preserve
336+
// `undefined` so the `error.message ?? "Internal error (CODE)"` fallback
337+
// in createErrorTaskError still kicks in (empty string is not nullish).
335338
return {
336339
type: "INTERNAL_ERROR",
337340
code: error.code,
338-
message: truncateMessage(error.message?.replace(/\0/g, "")),
339-
stackTrace: truncateStack(error.stackTrace?.replace(/\0/g, "")),
341+
message:
342+
error.message != null
343+
? truncateMessage(error.message.replace(/\0/g, ""))
344+
: undefined,
345+
stackTrace:
346+
error.stackTrace != null
347+
? truncateStack(error.stackTrace.replace(/\0/g, ""))
348+
: undefined,
340349
};
341350
}
342351
}

packages/core/test/errors.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,36 @@ describe("sanitizeError truncation", () => {
195195
});
196196
});
197197

198+
describe("sanitizeError INTERNAL_ERROR optional fields", () => {
199+
it("preserves undefined message (does not convert to empty string)", () => {
200+
const result = sanitizeError({
201+
type: "INTERNAL_ERROR",
202+
code: "SOME_INTERNAL_CODE" as any,
203+
// message and stackTrace intentionally undefined
204+
});
205+
206+
if (result.type === "INTERNAL_ERROR") {
207+
// Must stay undefined so `error.message ?? fallback` works downstream
208+
expect(result.message).toBeUndefined();
209+
expect(result.stackTrace).toBeUndefined();
210+
}
211+
});
212+
213+
it("truncates INTERNAL_ERROR message when present", () => {
214+
const result = sanitizeError({
215+
type: "INTERNAL_ERROR",
216+
code: "SOME_INTERNAL_CODE" as any,
217+
message: "x".repeat(5000),
218+
});
219+
220+
if (result.type === "INTERNAL_ERROR") {
221+
expect(result.message).toBeDefined();
222+
expect(result.message!.length).toBeLessThan(1100);
223+
expect(result.message).toContain("...[truncated]");
224+
}
225+
});
226+
});
227+
198228
describe("truncateStack message line bounding", () => {
199229
it("truncates huge error messages embedded in the stack", () => {
200230
// V8 format: "Error: <message>\n at ..."

0 commit comments

Comments
 (0)