Skip to content

fix(cron): preserve enabled-with-defaults failure alert through store roundtrip (fixes #96589) (AI-assisted)#96601

Closed
liuhao1024 wants to merge 1 commit into
openclaw:mainfrom
liuhao1024:fix/cron-failure-alert-defaults-roundtrip
Closed

fix(cron): preserve enabled-with-defaults failure alert through store roundtrip (fixes #96589) (AI-assisted)#96601
liuhao1024 wants to merge 1 commit into
openclaw:mainfrom
liuhao1024:fix/cron-failure-alert-defaults-roundtrip

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What Problem This Solves

Enabling cron failure alerts with defaults only (openclaw cron edit <job> --failure-alert with no --failure-alert-* flags) works until the next gateway restart, then silently stops firing forever. The store codec persists the enabled-with-defaults state but cannot read it back, so on reload the job's failureAlert becomes undefined and failing jobs fail invisibly.

Why This Change Was Made

failureAlertFromRow in src/cron/store/failure-alert-codec.ts checks whether all explicit config columns are null to decide if there is no alert config. But when failure_alert_disabled === 0 (the enabled-with-defaults sentinel), all explicit columns are also null, so the empty-check returns undefined instead of {}. This collapses three intended states (disabled / enabled-with-defaults / populated) into two on read.

The fix adds row.failure_alert_disabled !== 0 to the empty-column guard so the enabled sentinel survives the SQLite round-trip.

User Impact

Operators who enable per-job failure alerts with default settings will no longer silently lose alert coverage after a gateway restart. This is a data-roundtrip correctness fix with no config surface or behavioral change for alerts that were already configured with explicit fields.

Evidence

  • Behavior addressed: Cron failure-alert enabled-with-defaults ({}) collapses to undefined on SQLite store roundtrip, silencing alerts after gateway restart.
  • Environment tested: macOS 26.4.1, Node 22.22.3, OpenClaw repo at 0a042f68.
  • Steps run after the patch: Ran codec roundtrip verification importing bindFailureAlertColumns and failureAlertFromRow from src/cron/store/failure-alert-codec.ts with tsx.
  • Evidence after fix:
PASS: disabled (false) → false
PASS: undefined (no config) → undefined
PASS: enabled-with-defaults ({}) → {}
PASS: populated config → {"after":3,"channel":"telegram"}

4/4 passed
  • Observed result after fix: roundtrip({}) now returns {} (truthy, alert active) instead of undefined (falsy, alert silenced). The resolveFailureAlert function at src/cron/service/failure-alerts.ts:75 will proceed with defaults when jobConfig is {}.
  • What was not tested: Live gateway restart cycle (requires full gateway orchestration); the fix is validated at the codec layer where the bug resides.

Real behavior proof

  • Behavior addressed: Cron failure-alert enabled-with-defaults ({}) collapses to undefined on SQLite store roundtrip, silencing alerts after gateway restart.
  • Environment tested: macOS 26.4.1, Node 22.22.3, OpenClaw repo at 0a042f68.
  • Steps run after the patch: Ran codec roundtrip verification importing bindFailureAlertColumns and failureAlertFromRow from src/cron/store/failure-alert-codec.ts with tsx.
  • Evidence after fix:
PASS: disabled (false) → false
PASS: undefined (no config) → undefined
PASS: enabled-with-defaults ({}) → {}
PASS: populated config → {"after":3,"channel":"telegram"}

4/4 passed
  • Observed result after fix: roundtrip({}) now returns {} (truthy, alert active) instead of undefined (falsy, alert silenced). The resolveFailureAlert function at src/cron/service/failure-alerts.ts:75 will proceed with defaults when jobConfig is {}.

  • What was not tested: Live gateway restart cycle (requires full gateway orchestration); the fix is validated at the codec layer where the bug resides.

  • AI-assisted (Hermes Agent)

… roundtrip

failureAlertFromRow returned undefined when failure_alert_disabled was 0
(enabled-with-defaults) and all explicit config columns were null. This
caused resolveFailureAlert to treat the job as having no alert config,
silencing failure notifications after a gateway restart.

Add row.failure_alert_disabled !== 0 to the empty-column guard so the
enabled sentinel survives the SQLite round-trip as {}.

Fixes openclaw#96589
@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 8:20 PM ET / 00:20 UTC.

Summary
The branch updates cron failure-alert SQLite decoding so failureAlert: {} reads back as enabled and adds focused codec round-trip tests.

PR surface: Source +1, Tests +55. Total +56 across 2 files.

Reproducibility: yes. at source level: encode failureAlert: {} through bindFailureAlertColumns, decode it through current-main failureAlertFromRow, and observe it becomes undefined instead of {}. I did not run a live gateway restart in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #96589
Summary: This PR is the candidate fix for the defaults-only cron failure-alert reload bug tracked by the canonical issue; other failure-alert reports cover adjacent delivery-status or stalled-runtime paths.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] No full gateway restart proof was run on the PR branch; confidence comes from the exact SQLite codec/reload source path, focused tests, and copied after-fix runtime output.

Maintainer options:

  1. Decide the mitigation before merge
    Land the narrow codec/test fix after maintainer review and CI, then close cron: failure alerts enabled with defaults (--failure-alert) silently stop firing after gateway restart #96589 as fixed.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No automated repair lane is needed because the PR already implements the narrow code and test fix; remaining work is maintainer review and CI gating.

Security
Cleared: The diff only changes cron TypeScript codec logic and adds a Vitest test file; no security or supply-chain concern was found.

Review details

Best possible solution:

Land the narrow codec/test fix after maintainer review and CI, then close #96589 as fixed.

Do we have a high-confidence way to reproduce the issue?

Yes at source level: encode failureAlert: {} through bindFailureAlertColumns, decode it through current-main failureAlertFromRow, and observe it becomes undefined instead of {}. I did not run a live gateway restart in this read-only review.

Is this the best way to solve the issue?

Yes. The writer already encodes failure_alert_disabled === 0 as the enabled sentinel, so preserving that sentinel in the decoder is the narrowest maintainable fix without adding runtime fallback logic or config surface.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 0a14444924e3.

Label changes

Label justifications:

  • P1: The PR fixes a user-enabled cron alert path that can silently stop notifying after gateway restart, breaking an operator-facing notification workflow.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix copied terminal output from a direct runtime codec round-trip check showing defaults-only alerts decode as {}; a full gateway restart was not shown.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix copied terminal output from a direct runtime codec round-trip check showing defaults-only alerts decode as {}; a full gateway restart was not shown.
Evidence reviewed

PR surface:

Source +1, Tests +55. Total +56 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 55 0 +55
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 57 1 +56

What I checked:

  • Root policy read: Root AGENTS.md was read fully; there is no scoped AGENTS.md for src/cron, so the root review, source/history, and read-only requirements applied. (AGENTS.md:1, 0a14444924e3)
  • Current main writes the enabled sentinel: bindFailureAlertColumns({}) writes failure_alert_disabled: 0 while every explicit alert option can remain null. (src/cron/store/failure-alert-codec.ts:33, 0a14444924e3)
  • Current main drops that sentinel on read: The empty-column guard returns undefined when all explicit fields are null without checking failure_alert_disabled === 0, so defaults-only alerts are lost on reload. (src/cron/store/failure-alert-codec.ts:49, 0a14444924e3)
  • Runtime reload consumes the lossy decoder: rowToCronJob calls failureAlertFromRow and only includes failureAlert when the decoded value is not undefined, so the scheduler sees no per-job alert after the lossy row read. (src/cron/store/row-codec.ts:218, 0a14444924e3)
  • Resolver distinguishes {} from absent config: resolveFailureAlert returns null when job config is absent and global alerts are disabled, while an empty object proceeds to default alert policy. (src/cron/service/failure-alerts.ts:75, 0a14444924e3)
  • CLI can create defaults-only alerts: openclaw cron edit <job> --failure-alert with no nested alert flags creates an empty failureAlert object and sends it in the patch. (src/cli/cron-cli/register.cron-edit.ts:541, 0a14444924e3)

Likely related people:

  • steipete: Authored and merged the cron SQLite persistence migration and follow-up cron store refactors that own the row/codec path this PR fixes. (role: recent persistence owner; confidence: high; commits: 005da57957c9, 2bd07eead7e7, b222b5f6faac; files: src/cron/store/failure-alert-codec.ts, src/cron/store/row-codec.ts, src/cron/store.ts)
  • Shakker: Current blame for the failure-alert codec and row-codec lines points to the source-import/refactor commit carrying this exact implementation on main. (role: current codec line author; confidence: medium; commits: f2bc159b79c3; files: src/cron/store/failure-alert-codec.ts, src/cron/store/row-codec.ts, src/cron/service/failure-alerts.ts)
  • 0xbrak: Authored the original configurable cron failure-alert feature and per-job alert contract in the merged feature PR. (role: introduced behavior; confidence: medium; commits: e6fbcccae808, 4637b90c070f; files: src/cli/cron-cli/register.cron-edit.ts, src/cron/service.failure-alert.test.ts, src/cron/types.ts)
  • Takhoffman: Merged the original failure-alert feature PR and authored the follow-up alerting-semantics commit in that branch. (role: feature merger and adjacent contributor; confidence: medium; commits: f99ba28d4b81, 4637b90c070f; files: src/cli/cron-cli/register.cron-edit.ts, src/cron/service.failure-alert.test.ts, src/cron/types.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels Jun 25, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper automerge is enabled.

Draft PRs stay fix-only until GitHub marks them ready for review. Pause with /clawsweeper stop.

Automerge progress:

  • 2026-06-25 00:54:28 UTC review queued d7cd4f23c1f5 (queued)

@clawsweeper clawsweeper Bot added the clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge label Jun 25, 2026
@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the work here. ClawSweeper could not write to the source branch, so it opened a replacement PR rather than letting the fix drift. attribution still points back here.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #96615
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this source PR because this run explicitly enabled source-PR closeout.
Contributor credit is carried into the replacement PR body and release-note context.
Co-author credit kept:

fish notes: reasoning high; reviewed against bd9b2a1.

@clawsweeper clawsweeper Bot closed this Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants