fix(agents): skip bootstrap files with undefined path (#22698)

* fix(agents): skip bootstrap files with undefined path

buildBootstrapContextFiles() called file.path.replace() without checking
that path was defined. If a hook pushed a bootstrap file using 'filePath'
instead of 'path', the function threw TypeError and crashed every agent
session — not just the misconfigured hook.

Fix: add a null-guard before the path.replace() call. Files with undefined
path are skipped with a warning so one bad hook can't take down all agents.

Also adds a test covering the undefined-path case.

Fixes #22693

* fix: harden bootstrap path validation and report guards (#22698) (thanks @arosstale)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Artale
2026-02-22 13:17:07 +01:00
committed by GitHub
parent 45d7776697
commit 51e9c54f09
7 changed files with 141 additions and 8 deletions

View File

@@ -24,6 +24,33 @@ function registerExtraBootstrapFileHook() {
});
}
function registerMalformedBootstrapFileHook() {
registerInternalHook("agent:bootstrap", (event) => {
const context = event.context as AgentBootstrapHookContext;
context.bootstrapFiles = [
...context.bootstrapFiles,
{
name: "EXTRA.md",
filePath: path.join(context.workspaceDir, "BROKEN.md"),
content: "broken",
missing: false,
} as unknown as WorkspaceBootstrapFile,
{
name: "EXTRA.md",
path: 123,
content: "broken",
missing: false,
} as unknown as WorkspaceBootstrapFile,
{
name: "EXTRA.md",
path: " ",
content: "broken",
missing: false,
} as unknown as WorkspaceBootstrapFile,
];
});
}
describe("resolveBootstrapFilesForRun", () => {
beforeEach(() => clearInternalHooks());
afterEach(() => clearInternalHooks());
@@ -36,6 +63,23 @@ describe("resolveBootstrapFilesForRun", () => {
expect(files.some((file) => file.path === path.join(workspaceDir, "EXTRA.md"))).toBe(true);
});
it("drops malformed hook files with missing/invalid paths", async () => {
registerMalformedBootstrapFileHook();
const workspaceDir = await makeTempWorkspace("openclaw-bootstrap-");
const warnings: string[] = [];
const files = await resolveBootstrapFilesForRun({
workspaceDir,
warn: (message) => warnings.push(message),
});
expect(
files.every((file) => typeof file.path === "string" && file.path.trim().length > 0),
).toBe(true);
expect(warnings).toHaveLength(3);
expect(warnings[0]).toContain('missing or invalid "path" field');
});
});
describe("resolveBootstrapContextForRun", () => {