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 <steipete@gmail.com>
This commit is contained in:
zhouhe-xydt
2026-05-23 20:37:03 +08:00
committed by GitHub
parent eca9645365
commit 25fa46bd61
4 changed files with 42 additions and 4 deletions

View File

@@ -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",

View File

@@ -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[] {

View File

@@ -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 = {

View File

@@ -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 {