fix(cron): honor configured retry.backoffMs for recurring error backoff floor#93051
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 19, 2026, 3:26 PM ET / 19:26 UTC. Summary PR surface: Source +3, Tests +80. Total +83 across 2 files. Reproducibility: yes. Current main is source-reproducible: the recurring apply-time floor omits configured 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 the focused call-site fix after exact-head checks pass, preserving the existing documented Do we have a high-confidence way to reproduce the issue? Yes. Current main is source-reproducible: the recurring apply-time floor omits configured Is this the best way to solve the issue? Yes. Passing the existing configured schedule into the one divergent AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against efc36d71bdad. Label changesLabel justifications:
Evidence reviewedPR surface: Source +3, Tests +80. Total +83 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
|
|
Upgraded the proof from unit-level to a service-level run with SQLite readback: a recurring job with @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@vincentkoc thanks for landing #93333! 🦞 When you're next sweeping the ready queue, #93051 is another ready diamond fix: it honors the configured |
c6d4b97 to
8517995
Compare
8517995 to
66f7fd2
Compare
66f7fd2 to
e7dc1e6
Compare
e7dc1e6 to
b590d34
Compare
feb6ecf to
da4eeef
Compare
da4eeef to
15155ca
Compare
…SQLite readback Complement the unit-level applyJobResult test with a service-level proof: a recurring job configured with cronConfig.retry.backoffMs, hit by a permanent error through the real run() execution path, persists nextRunAtMs = endedAt + configured backoffMs into the SQLite-backed store (read back via loadCronStore), not the hardcoded default.
15155ca to
c8026d0
Compare
|
Maintainer verification complete.
Land-ready at |
|
Merged via squash.
Thanks @Alix-007! |
…ff floor (openclaw#93051) Merged via squash. Prepared head SHA: c8026d0 Co-authored-by: Alix-007 <[email protected]> Co-authored-by: steipete <[email protected]> Reviewed-by: @steipete
Summary
For recurring (
every/cron) cron jobs that error, the safety-net backoff floor that setsnextRunAtMswas computed with the hardcodedDEFAULT_ERROR_BACKOFF_SCHEDULE_MS, silently ignoring the operator-configuredcronConfig.retry.backoffMs. Every sibling path handling the same recurring-error backoff window passes the configured schedule (errorBackoffMs(..., retryConfig.backoffMs)on the retry-window branch;cronConfig?.retry?.backoffMs ?? DEFAULT...at theisErrorBackoffPending/deferPendingBackoffMissedCronSlotsscheduler gates). Only the recurring safety-net floor inapplyJobResultdiverged. Result: the persistednextRunAtMsis computed on the default schedule while the gate that decides runnability uses the configured one, so they disagree (early wasted wake-ups when config > default; over-long delays when config < default).What changed
applyJobResultnow passes the configured schedule to the recurring-error backoff floor:errorBackoffMs(job.state.consecutiveErrors ?? 1, state.deps.cronConfig?.retry?.backoffMs ?? DEFAULT_ERROR_BACKOFF_SCHEDULE_MS)— matching the sibling gate paths and the documentedcron.retry.backoffMsbehavior for recurring jobs.Real behavior proof
nextRunAtMs, ignoringcronConfig.retry.backoffMs; the scheduler gate enforced the configured window, so persisted next-run and gate disagreed.cbx_c1082c8bfe60(runrun_54ed64e26dcd). The proof drives the real cron service execution path end-to-end —run(state, jobId, "due")→executeJobCoreWithTimeout(realrunIsolatedAgentJobthrowing a permanent error) →applyJobResult→persistinto the SQLite-backed store — then readsnextRunAtMsback from SQLite vialoadCronStore. Nothing on the backoff/persistence path is mocked; only the agent runner throws, to produce the error result.node scripts/run-vitest.mjs src/cron/service/timer.backoff-config-readback.test.tscronConfig.retry.backoffMs=[300000]and anevery-1s job hit by a non-retryable permanent error, the value read back from the SQLite store isnextRunAtMs === endedAt + 300000(configured floor), withlastStatus === "error"andconsecutiveErrors === 1, instead ofendedAt + 30000(hardcoded default).timer.tsto the default-only floor (git checkout eefeab5766^ -- src/cron/service/timer.ts) and re-running the test fails withAssertionError: expected 1772452830000 to be 1772453100000— i.e. SQLite persistedendedAt+30000instead ofendedAt+300000— confirming the test is not a tautology and the fix is required. Restoring the fix turns it green.Tests and validation
node scripts/run-vitest.mjs src/cron/service/timer.backoff-config-readback.test.ts src/cron/service/timer.regression.test.ts→ 65 passed locally and on AWS Crabbox (realrun()execution + SQLite readback plus regression suite).tsgo:core:testclean on changed files;oxfmt --writeclean.Risk checklist
cron.retry.backoffMs.