From 1f6f8f9535baa756e75628cd0c7b4e65186b5dfc Mon Sep 17 00:00:00 2001 From: Vito Cappello Date: Tue, 28 Jul 2026 20:53:49 -0400 Subject: [PATCH] fix(agents): tolerate path-only bootstrap hook files (#115395) Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com> --- src/agents/bootstrap-budget.test.ts | 33 +++++++++++++++++++++++++++++ src/agents/bootstrap-budget.ts | 15 +++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/agents/bootstrap-budget.test.ts b/src/agents/bootstrap-budget.test.ts index be3f29e30e08..6c1a0a0ad9d1 100644 --- a/src/agents/bootstrap-budget.test.ts +++ b/src/agents/bootstrap-budget.test.ts @@ -46,6 +46,39 @@ describe("buildBootstrapInjectionStats", () => { expect(stats[1]?.injectedChars).toBe(20); expect(stats[1]?.truncated).toBe(true); }); + + it("derives names for path-only files supplied by bootstrap hooks", () => { + const pathOnlyFile = { + path: "/tmp/SELF_IMPROVEMENT_REMINDER.md", + content: "remember", + missing: false, + } as unknown as WorkspaceBootstrapFile; + const injectedFiles = [ + { + path: "/tmp/SELF_IMPROVEMENT_REMINDER.md", + content: "remember", + }, + ]; + + const stats = buildBootstrapInjectionStats({ + bootstrapFiles: [pathOnlyFile], + injectedFiles, + }); + const analysis = analyzeBootstrapBudget({ + files: stats, + bootstrapMaxChars: 20_000, + bootstrapTotalMaxChars: 60_000, + }); + + expect(analysis.files).toEqual([ + expect.objectContaining({ + name: "SELF_IMPROVEMENT_REMINDER.md", + path: "/tmp/SELF_IMPROVEMENT_REMINDER.md", + injectedChars: 8, + truncated: false, + }), + ]); + }); }); describe("analyzeBootstrapBudget", () => { diff --git a/src/agents/bootstrap-budget.ts b/src/agents/bootstrap-budget.ts index 75732e80a35a..98518ebd2951 100644 --- a/src/agents/bootstrap-budget.ts +++ b/src/agents/bootstrap-budget.ts @@ -166,16 +166,23 @@ export function buildBootstrapInjectionStats(params: { } return params.bootstrapFiles.map((file) => { const pathValue = normalizeOptionalString(file.path) ?? ""; + const normalizedPath = pathValue.replace(/\\/g, "/"); + // Bootstrap hooks are extension-facing and may provide path/content only. + // Derive the display name before budget classification so those entries + // keep working and cannot crash the turn when filename-specific caps run. + const name = + normalizeOptionalString(file.name) ?? + (normalizedPath ? path.posix.basename(normalizedPath) : "bootstrap"); const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length; const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? - injectedByPath.get(file.name) ?? - injectedByBaseName.get(file.name); + injectedByPath.get(name) ?? + injectedByBaseName.get(name); const injectedChars = injected ? injected.length : 0; const truncated = !file.missing && injectedChars < rawChars; return { - name: file.name, - path: pathValue || file.name, + name, + path: pathValue || name, missing: file.missing, rawChars, injectedChars,