Files
openclaw/src/agents/bootstrap-files.ts
Artale 51e9c54f09 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>
2026-02-22 13:17:07 +01:00

87 lines
2.7 KiB
TypeScript

import type { OpenClawConfig } from "../config/config.js";
import { applyBootstrapHookOverrides } from "./bootstrap-hooks.js";
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
import {
buildBootstrapContextFiles,
resolveBootstrapMaxChars,
resolveBootstrapTotalMaxChars,
} from "./pi-embedded-helpers.js";
import {
filterBootstrapFilesForSession,
loadWorkspaceBootstrapFiles,
type WorkspaceBootstrapFile,
} from "./workspace.js";
export function makeBootstrapWarn(params: {
sessionLabel: string;
warn?: (message: string) => void;
}): ((message: string) => void) | undefined {
if (!params.warn) {
return undefined;
}
return (message: string) => params.warn?.(`${message} (sessionKey=${params.sessionLabel})`);
}
function sanitizeBootstrapFiles(
files: WorkspaceBootstrapFile[],
warn?: (message: string) => void,
): WorkspaceBootstrapFile[] {
const sanitized: WorkspaceBootstrapFile[] = [];
for (const file of files) {
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
if (!pathValue) {
warn?.(
`skipping bootstrap file "${file.name}" — missing or invalid "path" field (hook may have used "filePath" instead)`,
);
continue;
}
sanitized.push({ ...file, path: pathValue });
}
return sanitized;
}
export async function resolveBootstrapFilesForRun(params: {
workspaceDir: string;
config?: OpenClawConfig;
sessionKey?: string;
sessionId?: string;
agentId?: string;
warn?: (message: string) => void;
}): Promise<WorkspaceBootstrapFile[]> {
const sessionKey = params.sessionKey ?? params.sessionId;
const bootstrapFiles = filterBootstrapFilesForSession(
await loadWorkspaceBootstrapFiles(params.workspaceDir),
sessionKey,
);
const updated = await applyBootstrapHookOverrides({
files: bootstrapFiles,
workspaceDir: params.workspaceDir,
config: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: params.agentId,
});
return sanitizeBootstrapFiles(updated, params.warn);
}
export async function resolveBootstrapContextForRun(params: {
workspaceDir: string;
config?: OpenClawConfig;
sessionKey?: string;
sessionId?: string;
agentId?: string;
warn?: (message: string) => void;
}): Promise<{
bootstrapFiles: WorkspaceBootstrapFile[];
contextFiles: EmbeddedContextFile[];
}> {
const bootstrapFiles = await resolveBootstrapFilesForRun(params);
const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {
maxChars: resolveBootstrapMaxChars(params.config),
totalMaxChars: resolveBootstrapTotalMaxChars(params.config),
warn: params.warn,
});
return { bootstrapFiles, contextFiles };
}