Skip to content

fix(cron): mark interrupted restart runs failed instead of lost#96174

Open
markoub wants to merge 7 commits into
openclaw:mainfrom
markoub:markoub/cron-interrupted-recovery
Open

fix(cron): mark interrupted restart runs failed instead of lost#96174
markoub wants to merge 7 commits into
openclaw:mainfrom
markoub:markoub/cron-interrupted-recovery

Conversation

@markoub

@markoub markoub commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Closes #96161

What Problem This Solves

Fixes an issue where users or operators inspecting cron task runs after a gateway restart could see an interrupted cron task marked as lost instead of failed when the scheduler reservation timestamp differed from the actual task run start timestamp.

Why This Change Was Made

Cron due-job and startup catch-up execution now persist the claimed run start timestamp before creating the task-ledger row, so restart task recovery uses the same timestamp as the cron task run id. If stop, restart recovery, or a newer cron generation wins during that short persistence window, the scheduler releases the claimed marker and does not emit a start event, create a task row, or start the job.

User Impact

Interrupted cron task rows recover to the terminal cron result instead of being mislabeled lost after restart. Operators also avoid new cron runs being admitted while restart recovery is taking over.

Evidence

AI assistance disclosure: This PR was prepared with Codex assistance; I reviewed the changes and validation.

Real behavior proof: In a local OpenClaw checkout, a runtime-level Node/TS proof command used a temporary redacted OPENCLAW_STATE_DIR, created a real SQLite-backed cron store, let the production cron timer path persist the due-job start marker and create the cron task row, simulated a gateway restart by clearing process-local cron/task mirrors while keeping the durable state, then ran production startup recovery and task-registry maintenance. This uses production cron/task registry modules; the isolated runner is held open only to simulate the interrupted run.

Redacted terminal output from the proof command:

{
  "proof": "OpenClaw #96174 runtime restart recovery",
  "tempState": "<redacted temp OPENCLAW_STATE_DIR>",
  "beforeRestart": {
    "cronRunningAtMs": 1782269100137,
    "taskRunId": "cron:proof-cron-restart:1782269100137",
    "taskStatus": "running",
    "taskStartedAt": 1782269100137,
    "markerMatchesTaskStartedAt": true,
    "runIdMatchesMarker": true
  },
  "afterStartupRecovery": {
    "cronRunningAtMs": null,
    "cronLastRunAtMs": 1782269100137,
    "cronLastRunStatus": "error",
    "cronLastError": "cron: job interrupted by gateway restart",
    "taskStatusBeforeMaintenance": "running"
  },
  "afterTaskMaintenance": {
    "summary": {
      "reconciled": 0,
      "recovered": 1,
      "cleanupStamped": 0,
      "pruned": 0
    },
    "taskStatus": "failed",
    "taskError": "cron: job interrupted by gateway restart",
    "taskEndedAt": 1782269700137,
    "failedNotLost": true
  }
}

Focused validation commands:

  • node scripts/run-vitest.mjs src/cron/service/timer.regression.test.ts (73 tests passed)
  • node scripts/run-vitest.mjs src/tasks/task-registry.maintenance.issue-60299.test.ts (19 tests passed)
  • pnpm exec oxfmt --check src/cron/service/timer.ts src/cron/service/timer.regression.test.ts
  • node scripts/run-oxlint.mjs src/cron/service/timer.ts src/cron/service/timer.regression.test.ts
  • git diff --check origin/main...HEAD
  • pnpm tsgo:core:test
  • codex review --base origin/main (passed after addressing review findings)

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 12, 2026, 2:17 AM ET / 06:17 UTC.

Summary
The branch atomically aligns persisted cron run markers with task run timestamps, fences superseded startup reservations, reconciles concurrent cron-store writes, and exposes transactional delivery-target updates to the Telegram plugin through the plugin SDK.

PR surface: Source +527, Tests +1003, Docs 0, Other 0. Total +1530 across 20 files.

Reproducibility: yes. at source level. Current main still exposes the precise reservation/start timestamp divergence, while the PR's production-module proof demonstrates that aligning the durable marker changes the interrupted task outcome from lost to failed.

Review metrics: 1 noteworthy metric.

  • Plugin SDK surface: 1 public function export added. The transactional delivery-target updater becomes third-party compatibility surface and should remain intentional during conflict resolution.

Stored data model
Persistent data-model change detected: serialized state: src/cron/service.issue-regressions.test.ts, serialized state: src/cron/service/timer.regression.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #96161
Summary: This PR is the active candidate fix for the canonical reservation/start timestamp-divergence issue; the other linked items cover distinct startup-liveness and completed-run-log recovery 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: 🦞 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:

  • Resolve the current merge conflict without dropping newer cron behavior.
  • Rerun the focused restart-recovery and concurrent Telegram writeback proof on the resolved exact head.

Risk before merge

  • [P1] Resolving the current conflict incorrectly could discard newer current-main cron scheduling, session, or persistence behavior because the overlap is in the same core modules.
  • [P1] The added plugin SDK function becomes supported third-party compatibility surface, even though its initial consumer is the bundled Telegram plugin.
  • [P1] Restart recovery and concurrent delivery-target writeback must be reproven on the conflict-resolved exact head; the current proof covers the existing PR head only.

Maintainer options:

  1. Resolve and revalidate the current head (recommended)
    Reconcile the branch with current main while preserving newer cron behavior, then rerun focused restart-recovery, Telegram writeback, SDK export, and exact-head checks before merge.
  2. Pause if the conflict changes the design
    Hold the PR for another state-model review if current-main cron refactors prevent retaining the atomic claim and transactional delivery-update invariants cleanly.

Next step before merge

  • [P2] The next action is maintainer-led conflict resolution across actively changed cron state code, followed by exact-head validation; there is no isolated mechanical defect for an automated repair worker.

Security
Cleared: The patch changes TypeScript runtime, SQLite-backed state handling, tests, docs, and SDK exports without adding dependencies, workflow execution, secret access, permissions, downloads, or package-resolution changes.

Review details

Best possible solution:

Port the atomic durable start claim and stale-reservation fencing onto current main’s canonical cron architecture, preserve concurrent delivery-target updates, keep the documented SDK helper as the approved narrow cross-package seam, and rerun restart plus concurrent-writeback proof on the resolved exact head.

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

Yes at source level. Current main still exposes the precise reservation/start timestamp divergence, while the PR's production-module proof demonstrates that aligning the durable marker changes the interrupted task outcome from lost to failed.

Is this the best way to solve the issue?

Yes. Persisting the claimed actual start before task-row creation fixes the recovery key at the cron owner boundary; the remaining work is conflict-safe integration and exact-head validation, not a different recovery heuristic.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: The PR fixes incorrect terminal state for restart-interrupted cron runs with bounded operator and task-history impact.
  • merge-risk: 🚨 compatibility: The branch adds a public plugin SDK function and must be reconciled with substantial newer cron behavior on current main.
  • merge-risk: 🚨 session-state: The patch changes durable cron reservation identity, task-ledger recovery, and concurrent persisted delivery state.
  • 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 (live_output): The PR body contains redacted live output from production SQLite-backed cron and task-registry modules showing matching marker/run timestamps and recovery of the interrupted task to failed rather than lost.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains redacted live output from production SQLite-backed cron and task-registry modules showing matching marker/run timestamps and recovery of the interrupted task to failed rather than lost.
Evidence reviewed

PR surface:

Source +527, Tests +1003, Docs 0, Other 0. Total +1530 across 20 files.

View PR surface stats
Area Files Added Removed Net
Source 10 680 153 +527
Tests 8 1044 41 +1003
Docs 1 1 1 0
Config 0 0 0 0
Generated 0 0 0 0
Other 1 2 2 0
Total 20 1727 197 +1530

What I checked:

  • Current-main bug remains: The due-job path persists runningAtMs from the reservation clock, then creates the task run using a later startedAt value; task maintenance still requires the recovered lastRunAtMs to equal execution.startedAt. (src/cron/service/timer.ts:1378, 2a02ab6bbe6a)
  • Recovery equality contract: Cron task-state recovery returns no result when the durable job timestamp differs from the task run's startedAt timestamp, confirming the reported failure mechanism. (src/tasks/task-registry.maintenance.ts:421, 2a02ab6bbe6a)
  • Root fix on PR head: The branch persists the actual startedAt value through an atomic compare-and-swap before creating the task row, then releases the claim when stop, restart recovery, generation fencing, or marker retirement supersedes admission. (src/cron/service/timer.ts:1973, f4e54c966795)
  • Real behavior proof: The PR body includes redacted production-module output showing the durable marker and task run ID use the same timestamp and that startup maintenance recovers the interrupted task as failed rather than lost. (f4e54c966795)
  • Current merge conflict: GitHub reports the current PR head as conflicting with merge state DIRTY, so the existing proof does not establish the behavior of a conflict-resolved head. (f4e54c966795)
  • Overlapping current-main development: Since the PR base, current main has materially changed the same cron service, persistence, timer, and row-codec files, making conflict resolution state-sensitive rather than mechanical drift in unrelated files. (src/cron/service/ops.ts:1, 2a02ab6bbe6a)

Likely related people:

  • steipete: Authored the branch commits implementing stale-reservation fencing, startup-generation fencing, atomic durable claims, delivery reconciliation, and distinct claimed timestamps; current-main blame also connects Peter Steinberger to the central cron and Telegram paths. (role: recent area contributor, PR assignee, and design author; confidence: high; commits: 6225d6ce95fa, 39ceecac024f, d50582625bf4; files: src/cron/service/timer.ts, src/cron/service/store.ts, src/cron/store.ts)
  • liaoandi: Authored the merged terminal-lost cron recovery work in fix(tasks): recover terminal lost cron rows #86088, which maintains the neighboring task-registry reconciliation path affected by the same durable cron/task identity boundary. (role: adjacent recovery contributor; confidence: medium; commits: be94853de0d0; files: src/tasks/task-registry.maintenance.ts, src/tasks/task-registry.maintenance.issue-60299.test.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.
Review history (2 earlier review cycles)
  • reviewed 2026-06-24T11:21:01.329Z sha a334c79 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T17:26:17.379Z sha 0017321 :: needs maintainer review before merge. :: none

@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. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jun 24, 2026
@markoub

markoub commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 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. 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. labels Jun 24, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete
steipete force-pushed the markoub/cron-interrupted-recovery branch from a334c79 to 27b9adc Compare July 9, 2026 17:14
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: telegram Channel integration: telegram scripts Repository scripts size: XL and removed size: L labels Jul 9, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jul 9, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

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

Labels

channel: telegram Channel integration: telegram docs Improvements or additions to documentation merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. scripts Repository scripts size: XL 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.

cron: restart-interrupted run mislabeled lost instead of failed (recovery matches reservation timestamp vs run id started-at)

2 participants