Skip to content

fix(cron): persist failure alert delivery status#95129

Closed
849261680 wants to merge 1 commit into
openclaw:mainfrom
849261680:fix/92058-cron-failure-alert-status
Closed

fix(cron): persist failure alert delivery status#95129
849261680 wants to merge 1 commit into
openclaw:mainfrom
849261680:fix/92058-cron-failure-alert-status

Conversation

@849261680

@849261680 849261680 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Persist per-job failureAlert delivery status from the actual alert request before emitting the cron finished event/run log.
  • Record alert send success as delivered, send failure as not-delivered, and bounded send timeout as unknown instead of leaving errored runs at not-requested.
  • Return accurate webhook failure-alert results for HTTP non-2xx responses instead of treating every completed fetch as delivered.

Fixes #92058.

Verification

  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/cron/service.persists-delivered-status.test.ts src/cron/service/failure-alerts.ts src/cron/service/ops.ts src/cron/service/state.ts src/cron/service/timer.ts src/gateway/server-cron-notifications.ts src/gateway/server-cron-notifications.test.ts --type-aware --report-unused-disable-directives-severity error --threads=1
  • git diff --check upstream/main..HEAD
  • node scripts/run-vitest.mjs src/cron/service.persists-delivered-status.test.ts src/cron/service.failure-alert.test.ts src/cron/service/timer.regression.test.ts src/gateway/server-cron-notifications.test.ts packages/gateway-protocol/src/cron-validators.test.ts src/cli/cron-cli.test.ts
  • pnpm tsgo:core
  • pnpm tsgo:core:test
  • .agents/skills/autoreview/scripts/autoreview --mode branch --base upstream/main (autoreview clean: no accepted/actionable findings reported)

Real behavior proof

Behavior addressed: Errored cron runs with configured failureAlert.after = 1 no longer persist the failure-notification status as not-requested when the alert path is actually requested.

Real environment tested: Local OpenClaw source checkout on macOS with an isolated OPENCLAW_STATE_DIR; the proof used CronService.run(job.id, "force"), the real runCronCommandJob child-process command runner, the production sendGatewayCronFailureAlert gateway webhook path, fetchWithSsrFGuard, cron store persistence, and SQLite cron run-log readback.

Exact steps or command run after this patch: OPENCLAW_STATE_DIR="$(mktemp -d /tmp/openclaw-95129-state.XXXXXX)" node --import tsx --input-type=module <proof script> plus the focused test command listed above.

Evidence after fix: Real proof output from the CronService.run(force) command-failure run. Temp paths are redacted; the raw command result shows the actual child process exited with code 7, and the finished event/job state/run log show the resulting failure-alert delivery status persisted as not-delivered.

{
  "scenario": "CronService.run(force) real command failure with per-job failureAlert.after=1",
  "head": "40dd4c72e73a12372f30157d863e0253f9d100f0",
  "stateDir": "<isolated-temp-state>",
  "storePath": "<isolated-temp-store>/cron/jobs.json",
  "job": {
    "name": "issue-92058-real-command-failure-alert-proof",
    "payloadKind": "command",
    "command": [
      "/bin/sh",
      "-lc",
      "printf proof-command-started; exit 7"
    ],
    "delivery": {
      "mode": "none"
    },
    "failureAlert": {
      "after": 1,
      "mode": "webhook",
      "channel": "telegram",
      "to": "http://127.0.0.1:9/openclaw-cron-proof",
      "cooldownMs": 0
    }
  },
  "rawCommandResult": {
    "status": "error",
    "error": "command exited with code 7",
    "summary": "proof-command-started",
    "diagnosticSummary": "proof-command-started",
    "exitCode": 7
  },
  "runResult": {
    "ok": true,
    "ran": true
  },
  "finishedEvent": {
    "action": "finished",
    "status": "error",
    "deliveryStatus": "not-requested",
    "failureNotificationDelivery": {
      "delivered": false,
      "status": "not-delivered",
      "error": "Blocked hostname or private/internal/special-use IP address"
    }
  },
  "persistedJobState": {
    "lastRunStatus": "error",
    "lastStatus": "error",
    "consecutiveErrors": 1,
    "lastDeliveryStatus": "not-requested",
    "lastFailureNotificationDelivered": false,
    "lastFailureNotificationDeliveryStatus": "not-delivered",
    "lastFailureNotificationDeliveryError": "Blocked hostname or private/internal/special-use IP address",
    "lastFailureAlertAtMsType": "number"
  },
  "persistedRunLog": {
    "action": "finished",
    "status": "error",
    "deliveryStatus": "not-requested",
    "failureNotificationDelivery": {
      "status": "not-delivered",
      "delivered": false,
      "error": "Blocked hostname or private/internal/special-use IP address"
    },
    "runId": "cron:<job-id>:<timestamp>"
  },
  "warningMessages": [
    "cron: job run returned error status",
    "cron: failure alert webhook blocked by SSRF guard"
  ]
}

Observed result after fix: For a failed job with failureAlert.after = 1 and no primary delivery, the job state/event/run log keep primary lastDeliveryStatus as not-requested while lastFailureNotificationDeliveryStatus records the actual alert path result. In the real guarded-webhook proof above, the persisted result is not-delivered; the regression tests also cover delivered on success and unknown on bounded timeout.

What was not tested: No packaged/Docker E2E lane or live external messaging channel was run. The live proof used a real local cron command failure and the production guarded webhook path from source, and channel delivery remains covered by the focused cron/gateway tests.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: M labels Jun 19, 2026
@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 21, 2026, 7:47 AM ET / 11:47 UTC.

Summary
The PR makes cron failure-alert senders return delivery outcomes, awaits the per-job failure-alert result, and persists delivered/not-delivered/unknown status before finished events and run logs are emitted.

PR surface: Source +95, Tests +176. Total +271 across 7 files.

Reproducibility: yes. source-level: current main records failure-notification status before the configured per-job failureAlert request result can return, matching the linked production report.

Review metrics: 1 noteworthy metric.

  • Failure-alert wait bound: 10,000 ms per requested alert. Status accuracy now depends on waiting for alert delivery before cron finalization, which is the main pre-merge availability decision.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #92058
Summary: This PR is the candidate fix for the canonical 2026.6.x cron failureAlert not-requested status issue; runtime-stall and thread-routing items remain separate.

Members:

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

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
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] Merging this PR intentionally lets cron run finalization wait up to 10,000 ms for each requested failure alert before finished events and run-log persistence.

Maintainer options:

  1. Accept The Bounded Wait
    Land the PR if maintainers are comfortable with cron completion finalization waiting up to 10 seconds per requested failure alert so durable status is accurate.
  2. Move Status Update After Finish
    Keep the requested status accuracy but update failure-alert delivery status through a separate post-finish persistence path so finished events are not delayed by alert transport.
  3. Pause For Scheduler Semantics
    Pause this PR if maintainers want failure-alert status persistence decided together with runtime-stall and thread-routing semantics instead of this bounded mitigation.

Next step before merge

  • [P2] Maintainers need to accept or redesign the bounded cron-finalization wait; there is no narrow automated repair recommended from this review.

Security
Cleared: No concrete security or supply-chain concern found; the diff changes no dependencies, workflows, lockfiles, secrets, or unguarded network path, and failure-alert webhooks still use the SSRF guard.

Review details

Best possible solution:

Land the status fix once maintainers accept the bounded wait; use a separate post-finish status update path if finished events must never wait on alert transport.

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

Yes, source-level: current main records failure-notification status before the configured per-job failureAlert request result can return, matching the linked production report.

Is this the best way to solve the issue?

Yes, if maintainers accept the bounded wait; otherwise the safer design is a post-finish persistence update that records alert status without delaying finished events.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: The PR targets configured cron failure alerts that can be missed or recorded as unrequested for real scheduled failures.
  • merge-risk: 🚨 availability: The diff adds a bounded wait to cron completion finalization before finished events and run-log persistence.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes redacted terminal output from a real local CronService.run(force) command failure using the production guarded webhook path and SQLite readback after the fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted terminal output from a real local CronService.run(force) command failure using the production guarded webhook path and SQLite readback after the fix.
Evidence reviewed

PR surface:

Source +95, Tests +176. Total +271 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 5 170 75 +95
Tests 2 177 1 +176
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 347 76 +271

What I checked:

  • Repository policy read: Root AGENTS.md was read fully and its ClawSweeper PR review, proof, compatibility, and merge-risk guidance applies to this cron/gateway review. (AGENTS.md:1, d1cbe29f3d52)
  • Scoped gateway policy read: The scoped Gateway guide was read fully; this PR touches gateway cron notification code and tests. (src/gateway/AGENTS.md:1, d1cbe29f3d52)
  • Current main status gap: Current main fire-and-forgets sendCronFailureAlert and only logs rejection, so the alert request result cannot be used for durable failure-notification status. (src/cron/service/failure-alerts.ts:131, d1cbe29f3d52)
  • Current main persistence order: Current main derives and persists failureNotificationDelivery before the asynchronous failure-alert send path can complete. (src/cron/service/timer.ts:703, d1cbe29f3d52)
  • Latest release still has the gap: The latest release tag v2026.6.9 still contains the fire-and-forget failure-alert sender and derived status persistence, so current shipped behavior does not already solve this PR's issue. (src/cron/service/failure-alerts.ts:131, c645ec4555c0)
  • PR head records actual alert outcome: The PR awaits sendCronFailureAlert with a 10,000 ms timeout and writes the returned delivery status into lastFailureNotificationDelivery* fields. (src/cron/service/failure-alerts.ts:141, 40dd4c72e73a)

Likely related people:

  • 0xbrak: Authored the merged configurable cron failure-alert feature that added the sendCronFailureAlert dependency and timer/gateway alert semantics. (role: introduced failure-alert behavior; confidence: high; commits: 4637b90c070f, e6fbcccae808; files: src/cron/service/timer.ts, src/cron/service/state.ts, src/gateway/server-cron.ts)
  • Takhoffman: Merged the original configurable failure-alert PR and co-authored follow-up alerting semantics in that feature history. (role: feature merger and follow-up contributor; confidence: medium; commits: 4637b90c070f, f99ba28d4b81; files: src/cron/service/timer.ts, src/gateway/server-cron.ts, src/cron/service.failure-alert.test.ts)
  • Evgeny Zislis: Authored the merged failure-destination expansion that added webhook mode, bestEffort handling, and adjacent status/routing behavior. (role: adjacent failure-destination contributor; confidence: medium; commits: 4b4ea5df8bc1; files: src/cron/delivery.ts, src/cron/service/timer.ts, src/gateway/server-cron.ts)
  • steipete: GitHub history shows Peter Steinberger/steipete carried the recent cron service split and SQLite runtime refactor around the active timer, failure-alert, and run-log surfaces. (role: recent cron persistence and module split owner; confidence: high; commits: 9d31cbbd6ad8, 2bd07eead7e7; files: src/cron/service/failure-alerts.ts, src/cron/service/timer.ts, src/gateway/server-cron-notifications.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 rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 19, 2026
@849261680

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@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. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. 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. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 20, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jul 11, 2026
@openclaw-barnacle

Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. 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: M stale Marked as stale due to inactivity 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.

failureAlert (after=1) never fires on 2026.6.5 — every errored run stuck at delivery_status 'not-requested'

1 participant