mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(doctor): warn and continue when cron job store is unreadable (#86384)
Catch non-ENOENT load failures inside maybeRepairLegacyCronStore so an unreadable ~/.openclaw/cron/jobs.json (e.g. root-owned 0600 inside Docker) no longer aborts the rest of the doctor health checks. The scheduler-side loadCronStore keeps its strict throw-on-read-failure contract. Closes #86102 Co-authored-by: 1052326311 <1052326311@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Docker E2E: dedupe scheduler lane resources so npm/service package lanes are not over-counted and serialized unnecessarily.
|
||||
- Crabbox: bootstrap Git metadata for sparse remote changed gates so raw synced workspaces can run `pnpm check:changed` from the intended diff.
|
||||
- xAI/LM Studio: avoid buffering ordinary bracketed or `final` prose until stream completion while watching for plain-text tool-call fallbacks.
|
||||
- Doctor: warn and continue when the cron job store exists but cannot be read (e.g. owned by another user with `0600` mode inside Docker) so later health checks still run. (#86102)
|
||||
- Discord: suppress a bot's previous reply body and referenced media from prompt context when a user replies to that bot message, while keeping reply metadata for routing. (#86238) Thanks @fuller-stack-dev.
|
||||
- Docker E2E: avoid rebuilding the Control UI twice while preparing the shared OpenClaw package tarball for package-backed scenario runs.
|
||||
- Tests: avoid rebuilding the Control UI twice during the installer Docker smoke now that `pnpm build` includes `ui:build`.
|
||||
|
||||
@@ -566,6 +566,29 @@ describe("maybeRepairLegacyCronStore", () => {
|
||||
expectNoteContaining("managed dreaming job", "Cron");
|
||||
expectNoteContaining("Rewrote 1 managed dreaming job", "Doctor changes");
|
||||
});
|
||||
|
||||
it("warns and continues when the cron job store cannot be read", async () => {
|
||||
const storePath = await makeTempStorePath();
|
||||
// Force loadCronStore to throw a non-ENOENT read error by placing a
|
||||
// directory where the cron job store file would be. This mirrors the
|
||||
// Docker-on-root permission failure reported in #86102 without depending
|
||||
// on the test runner's effective uid (root bypasses chmod gates).
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.mkdir(storePath);
|
||||
const prompter = makePrompter(true);
|
||||
|
||||
await expect(
|
||||
maybeRepairLegacyCronStore({
|
||||
cfg: { cron: { store: storePath } },
|
||||
options: {},
|
||||
prompter,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(prompter.confirm).not.toHaveBeenCalled();
|
||||
expectNoteContaining("Unable to read cron job store at", "Cron");
|
||||
expectNoteContaining("later health checks will continue", "Cron");
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy WhatsApp crontab health check", () => {
|
||||
|
||||
@@ -335,7 +335,21 @@ export async function maybeRepairLegacyCronStore(params: {
|
||||
prompter: Pick<DoctorPrompter, "confirm">;
|
||||
}) {
|
||||
const storePath = resolveCronStorePath(params.cfg.cron?.store);
|
||||
const store = await loadCronStore(storePath);
|
||||
let store: Awaited<ReturnType<typeof loadCronStore>>;
|
||||
try {
|
||||
store = await loadCronStore(storePath);
|
||||
} catch (err) {
|
||||
const reason = err instanceof Error ? err.message : String(err);
|
||||
note(
|
||||
[
|
||||
`Unable to read cron job store at ${shortenHomePath(storePath)}.`,
|
||||
`- ${reason}`,
|
||||
`Fix the file's permissions or contents and re-run ${formatCliCommand("openclaw doctor")}; later health checks will continue.`,
|
||||
].join("\n"),
|
||||
"Cron",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const rawJobs = (store.jobs ?? []) as unknown as Array<Record<string, unknown>>;
|
||||
if (rawJobs.length === 0) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user