fix(cron): treat exact-second cron slots as valid in stale-future repair#81731
Conversation
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>
|
Codex review: needs maintainer review before merge. Reviewed June 28, 2026, 6:13 PM ET / 22:13 UTC. Summary PR surface: Source +6, Tests +39. Total +45 across 2 files. Reproducibility: yes. source-reproducible. Current main still probes at Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest 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 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 changesLabel justifications:
Evidence reviewedPR surface: Source +6, Tests +39. Total +45 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
|
This pull request has been automatically marked as stale due to inactivity. |
…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>
…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)
…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>
…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>
…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>
Summary
isStaggeredCronRunAtMsprobes the cron library atrunAtMs + 1to decide whether a persistednextRunAtMsis a real schedule slot. Croner-style second-granular schedules normalize that 1ms probe back to the candidate's second, sopreviousRuns(1, runAtMs + 1)returns the slot before the candidate instead of the candidate itself.shouldRepairFutureCronNextRunAtMsthen classifies valid exact-second slots that fall two-plus intervals into the future as stale and rebases them.Probe at
runAtMs + 1_000instead so the previous-run lookup lands past the candidate second. This matches the+1_000cursor step used elsewhere in the same file (computeStaggeredCronNextRunAtMsline 109,computeStaggeredCronPreviousRunAtMsline 137).The bug only surfaces when
nextRunAtMsis two-plus intervals past the natural next slot, because closer cases are already saved by thenextRun === naturalNextandfollowingNaturalNextguards inshouldRepairFutureCronNextRunAtMs. The new regression test pins the path that the existing guards miss.Fixes #81691
Real behavior proof
Behavior addressed: A persisted exact-second cron
nextRunAtMsfalling two-plus schedule intervals into the future is misclassified as not-a-slot byisStaggeredCronRunAtMs, then rebased byshouldRepairFutureCronNextRunAtMsdespite being a valid scheduled slot. The user-visible symptom is an unexpected reschedule of a future cron firing for any cron job whose persistednextRunAtMssits more than one schedule interval ahead.Real environment tested: Local OpenClaw checkout on macOS 14 (arm64), Node 22.20.0, pnpm 11.1.0,
cronerresolved from the repo'spnpm-lock.yaml. No live gateway run was required because the misclassification reproduces directly against the samecronerlibrary the cron service uses at runtime; the regression test exercises the actualrecomputeNextRunsForMaintenancecode path on a realCronJobvalue, not a mock.Exact steps or command run after this patch:
cronerquirk against the issue's literal inputs by running a shortnodescript from the repo root with the in-treecronerresolution:src/cron/service/jobs.ts, re-ran the focused test (1 failure), restored, re-ran (56 passed).Evidence after fix:
cronerprobe (step 1) on this checkout shows the issue's reported behavior and that the new probe lands on the slot:Observed result after fix: The persisted
nextRunAtMs = 2026-05-08T19:00:00.000Zon a"0 9 * * *" Pacific/Honolulucron is now classified as a real schedule slot,shouldRepairFutureCronNextRunAtMsreturnsfalse,recomputeNextRunsForMaintenancereturnsfalse, 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, andpnpm tsgo:corelanes are clean.What was not tested:
pnpm check:changedend-to-end because thetsgo:test:srclane surfaces 9 pre-existing errors onorigin/mainfor 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 targetedpnpm tsgo:corelane passes clean.recomputeNextRunssuite.CI status note
The non-
Real behavior proofred checks on this PR are all already red on the latestorigin/mainhead and are unrelated to the cron change:checks-node-core-runtime-media-uiis failing onsrc/wizard/setup.official-plugins.test.ts:107(installs selected optional official plugins through the shared onboarding installer). The same shard fails onorigin/mainrun 25847797276 at the same line; locally the file passes 6/6.checks-node-core-support-boundaryis a cascade frombuild-artifacts'test/release-check.test.tsfailures at lines 505 and 538 (collectMissingPackPaths). Same two failures present onorigin/mainrun 25848244306.checks-node-coreis 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