Skip to content

fix(cron): treat exact-second cron slots as valid in stale-future repair#81731

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
yashkot007:fix/cron-exact-second-slots-81691
Jun 28, 2026
Merged

fix(cron): treat exact-second cron slots as valid in stale-future repair#81731
vincentkoc merged 2 commits into
openclaw:mainfrom
yashkot007:fix/cron-exact-second-slots-81691

Conversation

@yashkot007

@yashkot007 yashkot007 commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

isStaggeredCronRunAtMs probes the cron library at runAtMs + 1 to decide whether a persisted nextRunAtMs is a real schedule slot. Croner-style second-granular schedules normalize that 1ms probe back to the candidate's second, so previousRuns(1, runAtMs + 1) returns the slot before the candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs then classifies valid exact-second slots that fall two-plus intervals into the future as stale and rebases them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the candidate second. This matches the +1_000 cursor step used elsewhere in the same file (computeStaggeredCronNextRunAtMs line 109, computeStaggeredCronPreviousRunAtMs line 137).

The bug only surfaces when nextRunAtMs is two-plus intervals past the natural next slot, because closer cases are already saved by the nextRun === naturalNext and followingNaturalNext guards in shouldRepairFutureCronNextRunAtMs. The new regression test pins the path that the existing guards miss.

Fixes #81691

Real behavior proof

  • Behavior addressed: A persisted exact-second cron nextRunAtMs falling two-plus schedule intervals into the future is misclassified as not-a-slot by isStaggeredCronRunAtMs, then rebased by shouldRepairFutureCronNextRunAtMs despite being a valid scheduled slot. The user-visible symptom is an unexpected reschedule of a future cron firing for any cron job whose persisted nextRunAtMs sits more than one schedule interval ahead.

  • Real environment tested: Local OpenClaw checkout on macOS 14 (arm64), Node 22.20.0, pnpm 11.1.0, croner resolved from the repo's pnpm-lock.yaml. No live gateway run was required because the misclassification reproduces directly against the same croner library the cron service uses at runtime; the regression test exercises the actual recomputeNextRunsForMaintenance code path on a real CronJob value, not a mock.

  • Exact steps or command run after this patch:

    1. Reproduced the underlying croner quirk against the issue's literal inputs by running a short node script from the repo root with the in-tree croner resolution:
      node -e "import('croner').then(({Cron}) => { const c = new Cron('0 9 * * *', { timezone: 'Pacific/Honolulu' }); const slot = new Date('2026-05-08T19:00:00.000Z'); console.log('+1ms ->', c.previousRuns(1, new Date(slot.getTime() + 1))[0].toISOString()); console.log('+1s  ->', c.previousRuns(1, new Date(slot.getTime() + 1_000))[0].toISOString()); })"
      
    2. Ran the new regression test in isolation:
      node scripts/run-vitest.mjs src/cron/service.jobs.test.ts
      
    3. Ran the full cron lane to confirm no neighbours regress:
      node scripts/run-vitest.mjs src/cron/
      
    4. Lane-scoped lint/format/types:
      pnpm exec oxlint --config .oxlintrc.json src/cron/service/jobs.ts src/cron/service.jobs.test.ts
      pnpm exec oxfmt --check src/cron/service/jobs.ts src/cron/service.jobs.test.ts
      pnpm tsgo:core
      
    5. RED→GREEN proof: stashed only src/cron/service/jobs.ts, re-ran the focused test (1 failure), restored, re-ran (56 passed).
  • Evidence after fix:

    • Live croner probe (step 1) on this checkout shows the issue's reported behavior and that the new probe lands on the slot:
      +1ms -> 2026-05-07T19:00:00.000Z   ← pre-fix probe returns yesterday's slot
      +1s  -> 2026-05-08T19:00:00.000Z   ← post-fix probe returns the actual slot
      
    • Vitest output for the focused regression test (step 2):
       RUN  v4.1.6 /Users/yashwantkotipalli/Projects/openclaw
      
       Test Files  1 passed (1)
            Tests  56 passed (56)
         Start at  00:23:34
         Duration  177ms
      
    • Vitest output for the full cron lane (step 3):
       Test Files  91 passed (91)
            Tests  837 passed (837)
         Start at  00:24:32
         Duration  30.50s
      
    • RED proof — with the fix stashed:
       × preserves exact-second cron slots that fall multiple intervals into the future (#81691)
      AssertionError: expected true to be false
       Test Files  1 failed (1)
            Tests  1 failed | 55 passed (56)
      
  • Observed result after fix: The persisted nextRunAtMs = 2026-05-08T19:00:00.000Z on a "0 9 * * *" Pacific/Honolulu cron is now classified as a real schedule slot, shouldRepairFutureCronNextRunAtMs returns false, recomputeNextRunsForMaintenance returns false, and the timestamp is left intact instead of being rebased to the next natural slot. All 837 cron tests stay green and the targeted oxlint, oxfmt, and pnpm tsgo:core lanes are clean.

  • What was not tested:

    • I did not run pnpm check:changed end-to-end because the tsgo:test:src lane surfaces 9 pre-existing errors on origin/main for unrelated optional native deps (sharp, esbuild, baileys) that my checkout cannot install. The same 9 errors are present with my patch stashed; the patch introduces zero new diagnostics. The targeted pnpm tsgo:core lane passes clean.
    • I did not exercise this through a live cron gateway. The fix is a pure-arithmetic adjustment of a single private helper covered by the new regression test plus the existing 56-test recomputeNextRuns suite.

CI status note

The non-Real behavior proof red checks on this PR are all already red on the latest origin/main head and are unrelated to the cron change:

  • checks-node-core-runtime-media-ui is failing on src/wizard/setup.official-plugins.test.ts:107 (installs selected optional official plugins through the shared onboarding installer). The same shard fails on origin/main run 25847797276 at the same line; locally the file passes 6/6.
  • checks-node-core-support-boundary is a cascade from build-artifacts' test/release-check.test.ts failures at lines 505 and 538 (collectMissingPackPaths). Same two failures present on origin/main run 25848244306.
  • checks-node-core is the meta-aggregator for those two shards and turns red whenever either does.

The cron change does not touch wizard, release-check, or any shared boundary surface; the regression test is colocated with the file under change.

Notes

  • AI-assisted: drafted with Claude. Verified the cron-library probe behavior locally against the issue's exact inputs before opening, then again under stash/pop to prove RED→GREEN.
  • No changelog edit (per contributor guidance; maintainers/ClawSweeper handle the entry on landing).

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yashkot007
yashkot007 requested a review from a team as a code owner May 14, 2026 07:36
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. size: XS labels May 14, 2026
@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 28, 2026, 6:13 PM ET / 22:13 UTC.

Summary
The PR changes stale-future cron slot validation to probe one second past a candidate timestamp and adds a regression test preserving an exact-second Honolulu cron future slot.

PR surface: Source +6, Tests +39. Total +45 across 2 files.

Reproducibility: yes. source-reproducible. Current main still probes at runAtMs + 1, the repair path depends on that helper, Croner 10.0.1 operates on second boundaries, and the PR body includes RED-to-GREEN terminal proof; I did not run tests in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #81691
Summary: This PR is the open candidate fix for the exact-second cron stale-future issue; overflow-deferral reports are adjacent cron maintenance bugs with different state-preservation roots.

Members:

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

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
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.

Risk before merge

  • [P1] I did not execute tests in this read-only review; landing should still rely on exact-head CI or cron-lane proof against current main.

Maintainer options:

  1. Decide the mitigation before merge
    Land this focused helper/test change, or an equivalent probe-past-the-candidate-second fix with the same regression coverage, then close Cron future-slot repair misclassifies exact second cron slots #81691.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed; the branch already contains the focused fix, regression test, and proof, leaving maintainer review plus exact-head gates.

Security
Cleared: The diff only changes in-process cron timestamp comparison logic and a colocated test; it adds no dependency, lockfile, CI, secret, permission, network, or packaging surface.

Review details

Best possible solution:

Land this focused helper/test change, or an equivalent probe-past-the-candidate-second fix with the same regression coverage, then close #81691.

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

Yes, source-reproducible. Current main still probes at runAtMs + 1, the repair path depends on that helper, Croner 10.0.1 operates on second boundaries, and the PR body includes RED-to-GREEN terminal proof; I did not run tests in this read-only review.

Is this the best way to solve the issue?

Yes. Probing one second past the candidate is the narrowest maintainable fix because it preserves the existing stale-future repair policy and avoids broader schedule normalization or caller-specific exceptions.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a bounded cron scheduling correctness fix for unexpected rebasing of valid future job times with limited blast radius.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal proof for the Croner boundary, focused and cron-lane test output, lint/format/type commands, and stashed RED-to-GREEN proof for the changed behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal proof for the Croner boundary, focused and cron-lane test output, lint/format/type commands, and stashed RED-to-GREEN proof for the changed behavior.
Evidence reviewed

PR surface:

Source +6, Tests +39. Total +45 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 7 1 +6
Tests 1 39 0 +39
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 46 1 +45

What I checked:

  • AGENTS policy read: Root AGENTS.md was read fully; its read-beyond-diff, dependency-contract, no-mutation, and PR review guidance shaped this review. No scoped AGENTS.md owns src/cron, and the only maintainer note found was Telegram-specific. (AGENTS.md:1, a54206f0f5d1)
  • Current main still has the bug surface: isStaggeredCronRunAtMs still calls computeStaggeredCronPreviousRunAtMs(job, runAtMs + 1), and shouldRepairFutureCronNextRunAtMs depends on that helper before maintenance repair rebases a stored future timestamp. (src/cron/service/jobs.ts:182, a54206f0f5d1)
  • Maintenance caller path inspected: recomputeNextRunsForMaintenance defaults future cron repair on and calls shouldRepairFutureCronNextRunAtMs, so read/startup/timer maintenance callers can hit the classification path under review. (src/cron/service/jobs.ts:673, a54206f0f5d1)
  • PR patch is narrow: The proposed commit changes only the helper probe from runAtMs + 1 to runAtMs + 1_000 and documents the Croner second-boundary reason at the code site. (src/cron/service/jobs.ts:152, 5403f46e8467)
  • Focused regression coverage: The proposed test drives recomputeNextRunsForMaintenance with the linked Honolulu exact-second future slot and asserts the valid persisted timestamp is preserved. (src/cron/service.jobs.test.ts:925, 5403f46e8467)
  • Croner dependency contract checked: OpenClaw pins Croner 10.0.1; Croner previousRuns enumerates through _previous, and CronDate.decrement subtracts a second while resetting milliseconds to zero, supporting a full-second probe past the candidate boundary. (adc86215e92e)

Likely related people:

  • kevinslin: Authored and merged the stale-future cron repair PR that added isStaggeredCronRunAtMs and the original runAtMs + 1 probe. (role: introduced behavior; confidence: high; commits: 7175b1b5c634; files: src/cron/service/jobs.ts, src/cron/service.jobs.test.ts, src/cron/service/timer.ts)
  • yetval: Recently changed recomputeNextRunsForMaintenance options and adjacent startup deferral behavior that interacts with future-slot repair. (role: recent adjacent contributor; confidence: medium; commits: e896ca963430, b0520fa3202c; files: src/cron/service/jobs.ts, src/cron/service/ops.ts, src/cron/service/timer.ts)
  • steipete: Recent GitHub path history shows repeated cron schedule helper, runtime refactor, and test work around the same scheduling boundary. (role: recent area contributor; confidence: medium; commits: 31ce6dfc4c70, 33349269fd1d, e2c9c06de1c3; files: src/cron/schedule.ts, src/cron/service/jobs.ts, src/cron/service.jobs.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.

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 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 Jun 2, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 2, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 15, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 15, 2026
@vincentkoc
vincentkoc merged commit e99f254 into openclaw:main Jun 28, 2026
95 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 29, 2026
…air (openclaw#81731)

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jun 29, 2026
…air (openclaw#81731)

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
(cherry picked from commit e99f254)
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…air (openclaw#81731)

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…air (openclaw#81731)

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…air (openclaw#81731)

isStaggeredCronRunAtMs probed the cron library at runAtMs + 1 to decide
whether a persisted nextRunAtMs was a real schedule slot. Croner-style
second-granular schedules normalize that 1ms probe back to the candidate
second, so previousRuns(1, runAtMs + 1) returns the slot before the
candidate instead of the candidate itself. shouldRepairFutureCronNextRunAtMs
then classified valid exact-second slots two-plus intervals out as stale
and rebased them.

Probe at runAtMs + 1_000 instead so the previous-run lookup lands past the
candidate second, matching the +1s cursor step used elsewhere in this file.

Fixes openclaw#81691

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS 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 future-slot repair misclassifies exact second cron slots

2 participants