From 25fa46bd61ea7cefd4f2cf9ca7026fa41635e5ca Mon Sep 17 00:00:00 2001 From: zhouhe-xydt Date: Sat, 23 May 2026 20:37:03 +0800 Subject: [PATCH] fix(bootstrap): guard bootstrap name checks against undefined names (#85523) (#85615) * fix(bootstrap): guard bootstrap name checks against undefined names Add optional chaining to isAgentsBootstrapFile and isAgentsBootstrapName to prevent TypeError: Cannot read properties of undefined (reading 'toLowerCase') when bootstrap file entries have undefined name properties. This crash was observed in 2026.5.20 where a workspace bootstrap file entry with an undefined name caused every incoming message to fail during bootstrap context building, completely blocking all agent replies. Fixes #85523 * test(agents): cover unnamed bootstrap truncation entries * test(agents): keep bootstrap truncation fixture typed --------- Co-authored-by: Peter Steinberger --- src/agents/bootstrap-budget.test.ts | 23 +++++++++++++++++++ src/agents/bootstrap-budget.ts | 4 ++-- ...helpers.buildbootstrapcontextfiles.test.ts | 15 ++++++++++++ src/agents/pi-embedded-helpers/bootstrap.ts | 4 ++-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/agents/bootstrap-budget.test.ts b/src/agents/bootstrap-budget.test.ts index d12326b41813..a74c077ec345 100644 --- a/src/agents/bootstrap-budget.test.ts +++ b/src/agents/bootstrap-budget.test.ts @@ -103,6 +103,29 @@ describe("analyzeBootstrapBudget", () => { }); describe("bootstrap prompt warnings", () => { + it("handles malformed truncation entries without names", () => { + const analysis = analyzeBootstrapBudget({ + files: [ + { + name: "TEMP.md", + path: "/tmp/unknown", + missing: false, + rawChars: 10, + injectedChars: 1, + truncated: true, + }, + ], + bootstrapMaxChars: 5, + bootstrapTotalMaxChars: 5, + }); + (analysis.truncatedFiles[0] as { name?: string }).name = undefined; + + const lines = formatBootstrapTruncationWarningLines({ + analysis, + }); + expect(lines.join("\n")).toContain("10 raw -> 1 injected"); + }); + it("appends warning details to the turn prompt instead of mutating the system prompt", () => { const prompt = appendBootstrapPromptWarning("Please continue.", [ "AGENTS.md: 200 raw -> 0 injected", diff --git a/src/agents/bootstrap-budget.ts b/src/agents/bootstrap-budget.ts index a493d68d9b17..85c43173301e 100644 --- a/src/agents/bootstrap-budget.ts +++ b/src/agents/bootstrap-budget.ts @@ -68,8 +68,8 @@ function formatWarningCause(cause: BootstrapTruncationCause): string { return cause === "per-file-limit" ? "max/file" : "max/total"; } -function isAgentsBootstrapName(name: string): boolean { - return name.toLowerCase() === "agents.md"; +function isAgentsBootstrapName(name: string | undefined): boolean { + return name?.toLowerCase() === "agents.md"; } function normalizeSeenSignatures(signatures?: string[]): string[] { diff --git a/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts b/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts index f2644ab148be..0042ffbee406 100644 --- a/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts +++ b/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts @@ -241,6 +241,21 @@ describe("buildBootstrapContextFiles", () => { warnings.filter((warning) => !warning.includes('missing or invalid "path" field')), ).toStrictEqual([]); }); + + it("handles undefined file names without crashing", () => { + const fileWithUndefinedName = { + name: undefined, + path: "/tmp/test.md", + content: "content", + missing: false, + } as unknown as WorkspaceBootstrapFile; + const warnings: string[] = []; + const result = buildBootstrapContextFiles([fileWithUndefinedName], { + warn: (msg) => warnings.push(msg), + }); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); + }); }); type BootstrapLimitResolverCase = { diff --git a/src/agents/pi-embedded-helpers/bootstrap.ts b/src/agents/pi-embedded-helpers/bootstrap.ts index bd24fc866e93..80f24537b349 100644 --- a/src/agents/pi-embedded-helpers/bootstrap.ts +++ b/src/agents/pi-embedded-helpers/bootstrap.ts @@ -151,8 +151,8 @@ export function resolveBootstrapPromptTruncationWarningMode( return DEFAULT_BOOTSTRAP_PROMPT_TRUNCATION_WARNING_MODE; } -function isAgentsBootstrapFile(fileName: string): boolean { - return fileName.toLowerCase() === AGENTS_BOOTSTRAP_FILENAME.toLowerCase(); +function isAgentsBootstrapFile(fileName: string | undefined): boolean { + return fileName?.toLowerCase() === AGENTS_BOOTSTRAP_FILENAME.toLowerCase(); } function isPolicyDigestCandidate(line: string): boolean {