From a21144d8a606c9c0fc0c534877ebfe675508fa0d Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 01:17:31 +0000 Subject: [PATCH] fix(cron): preserve enabled-with-defaults failure alert through store roundtrip (fixes #96589) (AI-assisted) (#96615) Summary: - The PR preserves `failure_alert_disabled === 0` as the enabled-with-defaults failure-alert state and adds focused codec roundtrip tests. - PR surface: Source +2, Tests +54. Total +56 across 2 files. - Reproducibility: yes. At source level, current main encodes `failureAlert: {}` with `failure_alert_disabled = 0`, then decodes it as `undefined` when all explicit alert option columns are null. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head bd9b2a179853641432ad284b87658b77437c216a. - Required merge gates passed before the squash merge. Prepared head SHA: bd9b2a179853641432ad284b87658b77437c216a Review: https://github.com/openclaw/openclaw/pull/96615#issuecomment-4794949533 Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: liuhao1024 <11816344+liuhao1024@users.noreply.github.com> Approved-by: takhoffman --- src/cron/store/failure-alert-codec.test.ts | 54 ++++++++++++++++++++++ src/cron/store/failure-alert-codec.ts | 4 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/cron/store/failure-alert-codec.test.ts diff --git a/src/cron/store/failure-alert-codec.test.ts b/src/cron/store/failure-alert-codec.test.ts new file mode 100644 index 000000000000..2252592ed761 --- /dev/null +++ b/src/cron/store/failure-alert-codec.test.ts @@ -0,0 +1,54 @@ +// Unit tests for failure-alert SQLite column codec roundtrip. +import { describe, expect, it } from "vitest"; +import { bindFailureAlertColumns, failureAlertFromRow } from "./failure-alert-codec.js"; +import type { CronJobRow } from "./schema.js"; + +function roundtrip( + input: Parameters[0], +): ReturnType { + const columns = bindFailureAlertColumns(input); + return failureAlertFromRow(columns as CronJobRow); +} + +describe("failureAlertFromRow", () => { + it("round-trips disabled config (false)", () => { + expect(roundtrip(false)).toBe(false); + }); + + it("round-trips undefined (no alert config) as undefined", () => { + expect(roundtrip(undefined)).toBeUndefined(); + }); + + it("round-trips enabled-with-defaults ({}) as {}", () => { + const result = roundtrip({}); + expect(result).toEqual({}); + }); + + it("round-trips populated config with all fields", () => { + const config = { + after: 3, + cooldownMs: 120_000, + channel: "telegram" as const, + to: "@user", + mode: "announce" as const, + accountId: "acc-1", + includeSkipped: true, + }; + expect(roundtrip(config)).toEqual(config); + }); + + it("round-trips partial config (only after)", () => { + expect(roundtrip({ after: 5 })).toEqual({ after: 5 }); + }); + + it("enabled-with-defaults does not collapse to undefined on read", () => { + const columns = bindFailureAlertColumns({}); + const row = columns as CronJobRow; + expect(row.failure_alert_disabled).toBe(0); + expect(row.failure_alert_after).toBeNull(); + const decoded = failureAlertFromRow(row); + expect(decoded).toEqual({}); + expect(decoded).not.toBeUndefined(); + expect(decoded).toBeTruthy(); + }); +}); diff --git a/src/cron/store/failure-alert-codec.ts b/src/cron/store/failure-alert-codec.ts index 37eaffba98c8..b84c16f2cac6 100644 --- a/src/cron/store/failure-alert-codec.ts +++ b/src/cron/store/failure-alert-codec.ts @@ -46,6 +46,7 @@ export function failureAlertFromRow(row: CronJobRow): CronFailureAlert | false | if (row.failure_alert_disabled === 1) { return false; } + const failureAlertExplicitlyEnabled = row.failure_alert_disabled === 0; if ( row.failure_alert_after == null && !row.failure_alert_channel && @@ -53,7 +54,8 @@ export function failureAlertFromRow(row: CronJobRow): CronFailureAlert | false | row.failure_alert_cooldown_ms == null && row.failure_alert_include_skipped == null && !row.failure_alert_mode && - !row.failure_alert_account_id + !row.failure_alert_account_id && + !failureAlertExplicitlyEnabled ) { return undefined; }