mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:10:42 +00:00
fix(memory-core): suppress startup "cron service unavailable" warning (closes #69939)
memory-core registers a gateway:startup hook that runs reconcileManagedDreamingCron() before deps.cron is attached to the startup event (the startup hook is deferred via a 250ms setTimeout in server.impl). Downgrade the first startup-time "cron service unavailable" warning to a debug log, and rely on the existing runtime reconciliation path to warn if the cron service truly stays unavailable after boot. The managed dreaming cron job itself runs correctly — this was a log-noise regression, not a functional failure. Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
This commit is contained in:
committed by
Peter Steinberger
parent
f027d8faa7
commit
a37321ad5f
@@ -42,6 +42,7 @@ type DreamingPluginApiTestDouble = {
|
||||
|
||||
function createLogger() {
|
||||
return {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
@@ -1240,6 +1241,111 @@ describe("gateway startup reconciliation", () => {
|
||||
clearInternalHooks();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not emit the cron-unavailable warning on gateway:startup when deps.cron is missing (regression #69939)", async () => {
|
||||
clearInternalHooks();
|
||||
const logger = createLogger();
|
||||
const api: DreamingPluginApiTestDouble = {
|
||||
config: { plugins: { entries: {} } },
|
||||
pluginConfig: {},
|
||||
logger,
|
||||
runtime: {},
|
||||
registerHook: (event: string, handler: Parameters<typeof registerInternalHook>[1]) => {
|
||||
registerInternalHook(event, handler);
|
||||
},
|
||||
on: vi.fn(),
|
||||
};
|
||||
|
||||
try {
|
||||
registerShortTermPromotionDreamingForTest(api);
|
||||
// Simulate the startup race: gateway:startup fires before deps.cron is attached.
|
||||
await triggerInternalHook(
|
||||
createInternalHookEvent("gateway", "startup", "gateway:startup", {
|
||||
cfg: {
|
||||
hooks: { internal: { enabled: true } },
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-core": {
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
frequency: "15 4 * * *",
|
||||
timezone: "UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
deps: {},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(logger.warn).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("cron service unavailable"),
|
||||
);
|
||||
// The startup-path log should be demoted to debug instead.
|
||||
expect(logger.debug).toHaveBeenCalledWith(
|
||||
expect.stringContaining("cron service not yet available at gateway:startup"),
|
||||
);
|
||||
} finally {
|
||||
clearInternalHooks();
|
||||
}
|
||||
});
|
||||
|
||||
it("still warns on runtime reconciliation when cron remains unavailable (preserves #69939 genuine-failure signal)", async () => {
|
||||
clearInternalHooks();
|
||||
const logger = createLogger();
|
||||
const onMock = vi.fn();
|
||||
const api: DreamingPluginApiTestDouble = {
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-core": {
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
frequency: "15 4 * * *",
|
||||
timezone: "UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginConfig: {},
|
||||
logger,
|
||||
runtime: {},
|
||||
registerHook: (event: string, handler: Parameters<typeof registerInternalHook>[1]) => {
|
||||
registerInternalHook(event, handler);
|
||||
},
|
||||
on: onMock,
|
||||
};
|
||||
|
||||
try {
|
||||
registerShortTermPromotionDreamingForTest(api);
|
||||
// Startup without cron — must stay silent on warn.
|
||||
await triggerInternalHook(
|
||||
createInternalHookEvent("gateway", "startup", "gateway:startup", {
|
||||
cfg: api.config,
|
||||
deps: {},
|
||||
}),
|
||||
);
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
|
||||
// Now a runtime heartbeat reconciliation happens and cron is still missing
|
||||
// (e.g. the cron service genuinely failed to initialize). The warning must fire.
|
||||
const beforeAgentReply = getBeforeAgentReplyHandler(onMock);
|
||||
await beforeAgentReply(
|
||||
{ cleanedBody: "" },
|
||||
{ trigger: "heartbeat", workspaceDir: ".", sessionKey: "agent:main:main:heartbeat" },
|
||||
);
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("cron service unavailable"));
|
||||
} finally {
|
||||
clearInternalHooks();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("short-term dreaming trigger", () => {
|
||||
|
||||
@@ -718,10 +718,21 @@ export function registerShortTermPromotionDreaming(api: OpenClawPluginApi): void
|
||||
const cron = resolveCronServiceFromStartupSource(startupCronSource);
|
||||
const configKey = runtimeConfigKey(config);
|
||||
if (!cron && config.enabled && !unavailableCronWarningEmitted) {
|
||||
api.logger.warn(
|
||||
"memory-core: managed dreaming cron could not be reconciled (cron service unavailable).",
|
||||
);
|
||||
unavailableCronWarningEmitted = true;
|
||||
// The gateway emits `gateway:startup` via a deferred setTimeout, and
|
||||
// `deps.cron` may not be attached to the event context yet when memory-core's
|
||||
// startup hook fires (see issue #69939). Avoid logging a confusing warning on
|
||||
// the startup path — the runtime reconciliation path (heartbeat-driven) will
|
||||
// still warn if the cron service remains unavailable after boot.
|
||||
if (params.reason === "startup") {
|
||||
api.logger.debug?.(
|
||||
"memory-core: cron service not yet available at gateway:startup; deferring to runtime reconciliation.",
|
||||
);
|
||||
} else {
|
||||
api.logger.warn(
|
||||
"memory-core: managed dreaming cron could not be reconciled (cron service unavailable).",
|
||||
);
|
||||
unavailableCronWarningEmitted = true;
|
||||
}
|
||||
}
|
||||
if (cron) {
|
||||
unavailableCronWarningEmitted = false;
|
||||
|
||||
Reference in New Issue
Block a user