fix: dedupe repeated bootstrap truncation warnings (#67906) (thanks @rubencu)

* Agents: dedupe bootstrap truncation warnings

* Agents: normalize bootstrap warning cache bookkeeping

* fix(agents): scope bootstrap warning dedupe by workspace

* refactor(agents): simplify bootstrap warning wrapper

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
Rubén Cuevas
2026-04-17 01:24:15 -04:00
committed by GitHub
parent 3fe8b24c4e
commit 7e18c07e41
5 changed files with 106 additions and 3 deletions

View File

@@ -8,8 +8,10 @@ import {
} from "../hooks/internal-hooks.js";
import { makeTempWorkspace } from "../test-helpers/workspace.js";
import {
_resetBootstrapWarningCacheForTest,
FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE,
hasCompletedBootstrapTurn,
makeBootstrapWarn,
resolveBootstrapContextForRun,
resolveBootstrapFilesForRun,
resolveContextInjectionMode,
@@ -362,6 +364,69 @@ describe("hasCompletedBootstrapTurn", () => {
});
});
describe("makeBootstrapWarn", () => {
afterEach(() => {
_resetBootstrapWarningCacheForTest();
});
it("deduplicates repeated warnings for the same session and message", () => {
const warnings: string[] = [];
const warn = makeBootstrapWarn({
sessionLabel: "agent:main:test-session",
warn: (message) => warnings.push(message),
});
warn?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
warn?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
expect(warnings).toEqual([
"workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating (sessionKey=agent:main:test-session)",
]);
});
it("keeps warnings distinct across sessions", () => {
const warnings: string[] = [];
const first = makeBootstrapWarn({
sessionLabel: "agent:main:first-session",
warn: (message) => warnings.push(message),
});
const second = makeBootstrapWarn({
sessionLabel: "agent:main:second-session",
warn: (message) => warnings.push(message),
});
first?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
second?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
expect(warnings).toEqual([
"workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating (sessionKey=agent:main:first-session)",
"workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating (sessionKey=agent:main:second-session)",
]);
});
it("keeps warnings distinct across workspaces with the same session", () => {
const warnings: string[] = [];
const first = makeBootstrapWarn({
sessionLabel: "agent:main:shared-session",
workspaceDir: "/tmp/workspace-a",
warn: (message) => warnings.push(message),
});
const second = makeBootstrapWarn({
sessionLabel: "agent:main:shared-session",
workspaceDir: "/tmp/workspace-b",
warn: (message) => warnings.push(message),
});
first?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
second?.("workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating");
expect(warnings).toEqual([
"workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating (sessionKey=agent:main:shared-session)",
"workspace bootstrap file MEMORY.md is 36697 chars (limit 12000); truncating (sessionKey=agent:main:shared-session)",
]);
});
});
describe("resolveContextInjectionMode", () => {
it("defaults to always when config is missing", () => {
expect(resolveContextInjectionMode(undefined)).toBe("always");

View File

@@ -25,6 +25,29 @@ export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
const CONTINUATION_SCAN_MAX_TAIL_BYTES = 256 * 1024;
const CONTINUATION_SCAN_MAX_RECORDS = 500;
export const FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE = "openclaw:bootstrap-context:full";
const BOOTSTRAP_WARNING_DEDUPE_LIMIT = 1024;
const seenBootstrapWarnings = new Set<string>();
const bootstrapWarningOrder: string[] = [];
function rememberBootstrapWarning(key: string): boolean {
if (seenBootstrapWarnings.has(key)) {
return false;
}
if (seenBootstrapWarnings.size >= BOOTSTRAP_WARNING_DEDUPE_LIMIT) {
const oldest = bootstrapWarningOrder.shift();
if (oldest) {
seenBootstrapWarnings.delete(oldest);
}
}
seenBootstrapWarnings.add(key);
bootstrapWarningOrder.push(key);
return true;
}
export function _resetBootstrapWarningCacheForTest(): void {
seenBootstrapWarnings.clear();
bootstrapWarningOrder.length = 0;
}
export function resolveContextInjectionMode(config?: OpenClawConfig): AgentContextInjection {
return config?.agents?.defaults?.contextInjection ?? "always";
@@ -103,12 +126,21 @@ export async function hasCompletedBootstrapTurn(sessionFile: string): Promise<bo
export function makeBootstrapWarn(params: {
sessionLabel: string;
workspaceDir?: string;
warn?: (message: string) => void;
}): ((message: string) => void) | undefined {
if (!params.warn) {
const warn = params.warn;
if (!warn) {
return undefined;
}
return (message: string) => params.warn?.(`${message} (sessionKey=${params.sessionLabel})`);
const workspacePrefix = params.workspaceDir ?? "";
return (message: string) => {
const key = `${workspacePrefix}\u0000${params.sessionLabel}\u0000${message}`;
if (!rememberBootstrapWarning(key)) {
return;
}
warn(`${message} (sessionKey=${params.sessionLabel})`);
};
}
function sanitizeBootstrapFiles(

View File

@@ -91,6 +91,7 @@ export async function prepareCliRunContext(
sessionId: params.sessionId,
warn: prepareDeps.makeBootstrapWarn({
sessionLabel,
workspaceDir,
warn: (message) => cliBackendLog.warn(message),
}),
});

View File

@@ -465,6 +465,7 @@ export async function compactEmbeddedPiSessionDirect(
sessionId: params.sessionId,
warn: makeBootstrapWarn({
sessionLabel,
workspaceDir: effectiveWorkspace,
warn: (message) => log.warn(message),
}),
});

View File

@@ -464,7 +464,11 @@ export async function runEmbeddedAttempt(
config: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
warn: makeBootstrapWarn({ sessionLabel, warn: (message) => log.warn(message) }),
warn: makeBootstrapWarn({
sessionLabel,
workspaceDir: effectiveWorkspace,
warn: (message) => log.warn(message),
}),
contextMode: params.bootstrapContextMode,
runKind: params.bootstrapContextRunKind,
}),