fix: preserve disabled cron jobs on restore

This commit is contained in:
Tak Hoffman
2026-04-10 19:19:17 -05:00
parent 2a57127e52
commit 99fc830b73
2 changed files with 37 additions and 4 deletions

View File

@@ -133,4 +133,32 @@ describe("cron service store seam coverage", () => {
expect(raw.jobs[0]?.jobId).toBe("repro-stable-id");
expect(raw.jobs[0]?.id).toBeUndefined();
});
it("preserves disabled jobs when persisted booleans roundtrip through string values", async () => {
const { storePath } = await makeStorePath();
await writeSingleJobStore(storePath, {
id: "disabled-string-job",
name: "disabled string job",
enabled: "false",
createdAtMs: STORE_TEST_NOW - 60_000,
updatedAtMs: STORE_TEST_NOW - 60_000,
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "main",
wakeMode: "now",
payload: { kind: "systemEvent", text: "tick" },
state: {},
});
const before = await fs.readFile(storePath, "utf8");
const state = createStoreTestState(storePath);
await ensureLoaded(state);
const job = findJobOrThrow(state, "disabled-string-job");
expect(job.enabled).toBe(false);
const after = await fs.readFile(storePath, "utf8");
expect(after).toBe(before);
});
});

View File

@@ -1,5 +1,6 @@
import fs from "node:fs";
import { normalizeCronJobIdentityFields } from "../normalize-job-identity.js";
import { normalizeCronJobInput } from "../normalize.js";
import { loadCronStore, saveCronStore } from "../store.js";
import type { CronJob } from "../types.js";
import { recomputeNextRuns } from "./jobs.js";
@@ -34,11 +35,15 @@ export async function ensureLoaded(
const fileMtimeMs = await getFileMtimeMs(state.deps.storePath);
const loaded = await loadCronStore(state.deps.storePath);
const jobs = (loaded.jobs ?? []) as unknown as CronJob[];
for (const job of jobs) {
for (const [index, job] of jobs.entries()) {
const raw = job as unknown as Record<string, unknown>;
const { legacyJobIdIssue } = normalizeCronJobIdentityFields(raw);
const normalized = normalizeCronJobInput(raw);
const hydrated =
normalized && typeof normalized === "object" ? (normalized as unknown as CronJob) : job;
jobs[index] = hydrated;
if (legacyJobIdIssue) {
const resolvedId = typeof raw.id === "string" ? raw.id : undefined;
const resolvedId = typeof hydrated.id === "string" ? hydrated.id : undefined;
state.deps.log.warn(
{ storePath: state.deps.storePath, jobId: resolvedId },
"cron: job used legacy jobId field; normalized id in memory (run openclaw doctor --fix to persist canonical shape)",
@@ -46,8 +51,8 @@ export async function ensureLoaded(
}
// Persisted legacy jobs may predate the required `enabled` field.
// Keep runtime behavior backward-compatible without rewriting the store.
if (typeof job.enabled !== "boolean") {
job.enabled = true;
if (typeof hydrated.enabled !== "boolean") {
hydrated.enabled = true;
}
}
state.store = {