Repository navigation
fix(heartbeat): empty scheduled heartbeats time out behind busy queues and enter retry storms - #137517
Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs maintainer review before merge. Reviewed September 5, 2026, 2:49 PM ET / 18:49 UTC. ClawSweeper reviewWhat this changesThe PR makes scheduled heartbeats with empty stored checklists finish before busy-queue deferral, with complementary regression tests and documentation. Merge readiness✅ Ready for maintainer review This remains a useful, focused repair: current main still contains the ordering defect, and the supplied Gateway traces demonstrate the corrected settlement behavior. No actionable findings remain from this review. Priority: P1 Review scores
Verification
How this fits togetherThe automation scheduler submits heartbeat wakes to the shared heartbeat runner. The runner checks stored instructions and execution activity, then either records a skip or starts an agent turn; the wake owner decides whether to settle or retry. flowchart TD
A[Scheduled automation] --> B[Heartbeat wake owner]
B --> C[Scratch and event preflight]
C -->|Empty and no due tasks| D[Recorded terminal skip]
C -->|Work remains| E[Busy execution guards]
E -->|Busy| B
E -->|Available| F[Agent turn]
D --> G[Automation settles]
F --> G
Before mergeNone. Agent review detailsSecurityNone. PR surfaceSource +5, Tests +46, Docs 0. Total +51 across 4 files. View PR surface stats
Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Technical reviewBest possible solution: Keep one shared preflight predicate that settles empty monitors before contention while preserving deferral for actual work; the small growth expresses that ordering and its invariant without a parallel path. Do we have a high-confidence way to reproduce the issue? Yes: current-main source establishes that a scheduled empty monitor returns a retryable busy skip before scratch inspection. Contributor Gateway traces demonstrate the resulting timeout; this review did not execute a reproduction. Is this the best way to solve the issue? Yes: extending the existing owner-level preflight repairs the cause while preserving task/event handling. Changing watchdog duration or retry policy would leave empty monitors unnecessarily queued. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against 676845210acb. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (6 earlier review cycles)
|
5aaa62b to
0effee2
Compare
0effee2 to
df66b6d
Compare
System-owned heartbeat monitors whose monitor scratch is empty could be deferred behind shared busy guards (main queue, cron activity, active reply runs) before the runner resolved its empty-scratch preflight. The wake layer treated the resulting requests-in-flight skip as retryable, kept the settlement pending, and the owning automation waited until its fixed 600-second watchdog expired, then entered transient-error backoff across every configured agent. Resolve the heartbeat preflight before the shared busy guards for scheduled interval wakes without heartbeat tasks, so an effectively empty scratch terminates immediately with empty-heartbeat-file instead of retrying behind busy queues. Wakes with actionable scratch, missing scratch, queued events, or scheduled heartbeat tasks keep ordinary queue deferral. Fixes openclaw#137492 AI-assisted: repaired and verified with Codex.
df66b6d to
dc1ad6e
Compare
Merge current main while retaining the contributor's early empty-monitor preflight. Consolidate the complementary busy-queue tests around the existing scratch fixture and assert the recorded outcome for both cases. Document that task-free scheduled interval monitors settle before busy deferral. Fixes openclaw#137492 Co-authored-by: LiuwqGit <liu.weiqin@xydigit.com>
|
@clawsweeper re-review Integrated current main while preserving the original production repair and contributor ancestry. Head: 876ae2e. Consolidated the two regression cases around the existing scratch fixture, added the recorded-outcome assertion to both, and clarified the documented empty-monitor behavior. Independent source review and automated review found no actionable issues. The PR body retains and attributes the original Gateway before/after proof; fresh hosted CI is pending for this integrated head. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Merge the canonical main fix for the unrelated subagent authority fixture so fork CI can validate the heartbeat repair. Preserve the existing heartbeat implementation, regression tests, documentation and contributor ancestry without changing the assertions in the shared fixture. Related: openclaw#139176
|
Ready to land head The heartbeat wake owner now resolves the existing empty-monitor preflight before retryable busy guards for task-free intervals. Actionable or absent scratch, due tasks and event-triggered work retain their existing handling. The two regression cases share the existing scratch fixture and assert both the returned and recorded outcomes. Production is net +5 lines; tests net +46, with no new production test seam. The docs explain the no-op ordering. Inspected contributor Gateway proof shows 69 empty-monitor runs settling in 255–506 ms during a held model HTTP call, versus a baseline 600006 ms timeout. This proof remains explicitly attributed: the baseline commit was not recorded, and the endpoint was a local compatible test server, not an authenticated provider. Current-main source matches the reported defect; the original regression failed before the fix. Validation also included |
|
Merged via squash.
|
…s and enter retry storms (openclaw#137517) * fix(heartbeat): skip empty scheduled heartbeats before busy queue guards System-owned heartbeat monitors whose monitor scratch is empty could be deferred behind shared busy guards (main queue, cron activity, active reply runs) before the runner resolved its empty-scratch preflight. The wake layer treated the resulting requests-in-flight skip as retryable, kept the settlement pending, and the owning automation waited until its fixed 600-second watchdog expired, then entered transient-error backoff across every configured agent. Resolve the heartbeat preflight before the shared busy guards for scheduled interval wakes without heartbeat tasks, so an effectively empty scratch terminates immediately with empty-heartbeat-file instead of retrying behind busy queues. Wakes with actionable scratch, missing scratch, queued events, or scheduled heartbeat tasks keep ordinary queue deferral. Fixes openclaw#137492 AI-assisted: repaired and verified with Codex. * ci: re-trigger security-fast check (transient pnpm audit 503) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: LiuwqGit <liu.weiqin@xydigit.com>
Closes #137492
What Problem This Solves
empty-heartbeat-file. The pending wake settlement was retried every 60 seconds until the owning automation hit its fixed 600-second watchdog, was recorded astimeout, and entered transient-error backoff — a persistent retry storm across every configured agent during ordinary Gateway activity.clawsweeper:source-repro+clawsweeper:fix-shape-clearon the issue.Why This Change Was Made
empty-heartbeat-file(non-retryable; the wake layer settles it at once) instead of returning a retryablerequests-in-flight/cron-in-progressskip first.src/infra/heartbeat-runner-prompt.tsnow also coversintervalwakes; the resolved preflight is reused after the guards, matching the existing exec-event pattern. Per review feedback, the interval rule is folded into the existing predicate as a single expression with one concise invariant comment (no PR-specific commentary growth).requests-in-flight. All existing busy-guard and scratch-gating suites pass unchanged.User Impact
agents.defaults.heartbeat.everyconfigured and an empty/comments-only monitor scratch,openclaw automations runs --id <heartbeat-job-id>shows scheduled heartbeat runs finishing quickly withstatus=skipped, reason=empty-heartbeat-file(hundreds of ms, as before the regression) even while the Gateway is busy — no moreerrorReason=timeoutatdurationMs≈600000, no more "automations have failed 3+ runs in a row" retry storms inopenclaw doctor.src/infra/heartbeat-runner-prompt.ts(shouldPreflightWakeBeforeBusy),src/infra/heartbeat-runner-execution.ts(call site rename), and the two new cases insrc/infra/heartbeat-runner.skips-busy-session-lane.test.ts.Evidence
origin/mainand one on this PR branch, each with two agents:b-empty(heartbeatevery: "20s",timeoutSeconds: 600, comments-only monitor scratch seeded through the productionwriteCronJobScratchpath) anda-busy(an every-20s isolated automation whose real agent turn holds a model HTTP call on a local OpenAI-compatible endpoint for 15 minutes — real cron scheduler, real SQLite state, real wake settlement; no stubbed queue internals).Contributor Gateway trace on the original production patch (head
fbba43ffb4aabbde33f9006979fc414ee7c4fb73; same scenario as the main-build trace below):empty-heartbeat-fileskip before the busy guards, so the wake settles immediately (that reason is not in the retryable skip set) and the owning cron automation records a skip instead of timing out; on unmodified main the same live scenario reproduces both the 600-second watchdog timeout and the minutes-late deferred settlement. The complement test confirms actionable scratch behind the same busy queue still defers withrequests-in-flight.AssertionError: expected { status: 'skipped', reason: 'requests-in-flight' } to deeply equal { status: 'skipped', reason: 'empty-heartbeat-file' }— the deferred-wake-then-timeout chain reported in the issue.Maintainer Integration
The contributor's production fix is retained. The branch incorporates main
886aaa689076b7b7c2f7a4d8d0048e89f5855698while preserving contributor ancestry and main's newer conversation/event ownership. The two regression cases now share the existing scratch fixture and both assert the recorded heartbeat outcome; this removes 15 test lines without dropping either behavior. The heartbeat docs describe immediate empty-monitor settlement before busy deferral.Fresh independent automated review found no actionable P0–P2 findings. The integrated head
74676490d5548c658e4601eddbfbaeeef9964b88passed CI run 33984969269, including the previously failing shared fixture. The original contributor head passed CI run 33888672199. The original empty-scratch regression failed before the fix withrequests-in-flightinstead ofempty-heartbeat-file; actionable scratch retains busy deferral.The first integrated CI run 33979544632 passed all jobs except an unrelated subagent authority fixture and the aggregate gate. This branch now includes the canonical fix from #139176, whose unchanged completion assertion has a proven tolerance for cold two-CPU runners. The heartbeat patch is unchanged; fresh independent automated review of the integrated candidate again found no actionable findings.
Production delta: +16/-11 (net +5), expressing the one canonical pre-busy predicate and its invariant comment. Tests: +47/-1 (net +46). Docs: +1/-1. No configuration, storage schema, timeout, or retry-policy changes.
Original fix and Gateway reproduction by @LiuwqGit; reported by @creanet-64 in #137492. Related alternative #137936 is superseded once this fix lands.