fix(cron): clear unresolved next-run to stop refire loops (#2702)#2709
Merged
Conversation
Port upstream OpenClaw fix openclaw#66083 ("stop unresolved next-run refire loops") into the fork's consolidated cron service. When computeJobNextRunAtMs cannot resolve a recurring cron job's next run, applyJobResult no longer synthesizes a phantom run time — the MIN_REFIRE_GAP_MS guard on the success path, or the error backoff delay on the error path. A synthesized time looks "due" on the next tick and refires the job forever even though its schedule never resolved. Instead the schedule is cleared; the fork's existing armTimer maintenance recheck plus recomputeNextRunsForMaintenance (which always repairs a missing nextRunAtMs) re-arm the job so it fires again on a later tick once the next run becomes resolvable — rather than silently stalling. Adds resolveCronNextRunWithLowerBound, applied to cron-kind jobs only; "every" jobs keep the arithmetic backoff fallback. The two openclaw#30905 throw-path tests are updated to the reconciled behavior (clear + scheduleErrorCount tracking, matching upstream). Regression coverage: unit-level applyJobResult cases (openclaw#66019) plus an onTimer integration test asserting both the no-spurious-refire and fires-once-resolvable paths. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the upstream OpenClaw fix openclaw#66083 ("stop unresolved next-run refire loops") into the fork's consolidated cron service. Closes #2702.
Root cause. When
computeJobNextRunAtMscannot resolve a recurring cron job's next run (returnsundefined, or throws → caught),applyJobResultsynthesized a phantomnextRunAtMs:endedAt + MIN_REFIRE_GAP_MS(the 2 s spin-loop guard)endedAt + backoff(the error backoff delay)A synthesized time looks due on the next tick, so the job refires forever even though its schedule never actually resolved.
Fix. Add
resolveCronNextRunWithLowerBound(cron-kind only): when the natural next run is unresolved, clear the schedule (undefined) instead of synthesizing a time.everyjobs keep the arithmetic backoff fallback unchanged. The fork already has the recovery half —armTimerkeeps a maintenance recheck armed while enabled jobs exist, andrecomputeNextRunsForMaintenancealways repairs a missingnextRunAtMs— so once the masking phantom time is gone, an initially-unresolved job is correctly re-armed and fires on a later tick once its next run becomes resolvable, instead of silently stalling.Scope note (the "entangled refactor" the issue warns about)
The issue's referenced commit
e1fe71872c3and follow-up#66113are written against upstream's post-SQLite-migration cron service; their test-case-3 backoff-floor semantics depend on that larger timer refactor (which "does not apply cleanly to the fork"). This PR ports#66083— the coherent, minimal fix that satisfies both acceptance criteria — and intentionally does not drag in the SQLite-era evolution.Changes
src/cron/service/timer.ts— addresolveCronNextRunWithLowerBound; apply it in both branches ofapplyJobResultfor cron-kind jobs.src/cron/service.issue-66019-unresolved-next-run.test.ts(new) —onTimerintegration coverage: no spurious refire (success + errored), and reschedule-and-fire on a later tick once the next run resolves.src/cron/service.issue-regressions.test.ts— reconcile the two#30905throw-path tests to the new clear-on-unresolved behavior (matching upstream verbatim), and add the unit-level#66019applyJobResultregression cases.Acceptance criteria
Verification
src/cron/unit suite: 452 passed (49 files).pnpm check(format + tsgo typecheck + oxlint type-aware + fork lint gates): clean.src/cron/, notsrc/middleware/, so the LIVE smoke-test gate does not apply.🤖 Generated with Claude Code