diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c909066bb7..e2e6de66b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -181,6 +181,7 @@ Docs: https://docs.openclaw.ai - Memory/Hybrid recall: when strict hybrid scoring yields no hits, preserve keyword-backed matches using a text-weight floor so freshly indexed lexical canaries no longer disappear behind `minScore` filtering. (#29112) Thanks @ceo-nada. - Android/Notifications auth race: return `NOT_AUTHORIZED` when `POST_NOTIFICATIONS` is revoked between authorization precheck and delivery, instead of returning success while dropping the notification. (#30726) Thanks @obviyus. - Cron/Reminder session routing: preserve `job.sessionKey` for `sessionTarget="main"` runs so queued reminders wake and deliver in the originating scoped session/channel instead of being forced to the agent main session. +- Cron/Timezone regression guard: add explicit schedule coverage for `0 8 * * *` with `Asia/Shanghai` to ensure `nextRunAtMs` never rolls back to a past year and always advances to the next valid occurrence. (#30351) - Agents/Sessions list transcript paths: resolve `sessions_list` `transcriptPath` via agent-aware session path options and ignore combined-store sentinel paths (`(multiple)`) so listed transcript paths always point to the state directory. (#28379) Thanks @fafuzuoluo. - Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc. - Browser/Navigate: resolve the correct `targetId` in navigate responses after renderer swaps. (#25326) Thanks @stone-jin and @vincentkoc. diff --git a/src/cron/schedule.test.ts b/src/cron/schedule.test.ts index 6c132e28296..fd80fef42eb 100644 --- a/src/cron/schedule.test.ts +++ b/src/cron/schedule.test.ts @@ -13,6 +13,20 @@ describe("cron schedule", () => { expect(next).toBe(Date.parse("2025-12-17T17:00:00.000Z")); }); + it("does not roll back year for Asia/Shanghai daily cron schedules (#30351)", () => { + // 2026-03-01 08:00:00 in Asia/Shanghai + const nowMs = Date.parse("2026-03-01T00:00:00.000Z"); + const next = computeNextRunAtMs( + { kind: "cron", expr: "0 8 * * *", tz: "Asia/Shanghai" }, + nowMs, + ); + + // Next 08:00 local should be the following day, not a past year. + expect(next).toBe(Date.parse("2026-03-02T00:00:00.000Z")); + expect(next).toBeGreaterThan(nowMs); + expect(new Date(next ?? 0).getUTCFullYear()).toBe(2026); + }); + it("throws a clear error when cron expr is missing at runtime", () => { const nowMs = Date.parse("2025-12-13T00:00:00.000Z"); expect(() =>