#17801: fix(heartbeat): enforce interval guard for non-interval wake reasons
stale
size: XS
trusted-contributor
Cluster:
Heartbeat Management Improvements
Fixes #17797
## Problem
Non-interval wake reasons (`exec:*:exit`, `exec-event`, `cron:*`, `hook:wake`) bypassed the `nextDueMs` guard in the heartbeat runner's `run()` function. This meant every exec tool completion or sub-agent exit triggered a full heartbeat run regardless of the configured interval, causing runaway loops (244 runs in 2.5h instead of ~5).
## Root Cause
The interval check only applied when `reason === "interval"`:
```ts
if (isInterval && now < agent.nextDueMs) continue;
```
All other wake reasons skipped this guard entirely.
## Fix
Remove the `isInterval` condition so `nextDueMs` is respected for **all** wake reasons:
```ts
if (now < agent.nextDueMs) continue;
```
`requestHeartbeatNow()` still wakes the runner immediately, but if the agent already ran within its interval window the run is correctly skipped. This is a 1-line behavioral change (plus cleanup of the now-unused `isInterval` variable).
## Testing
All existing heartbeat tests pass (38/38).
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Enforces `nextDueMs` interval guard for all wake reasons (exec-event, cron, hooks), not just `interval` wakes. Previously, non-interval wake reasons bypassed the interval check entirely, causing heartbeats to run on every exec tool completion regardless of the configured interval (244 runs in 2.5h instead of ~5). Now all wake reasons respect the `nextDueMs` guard, while `requestHeartbeatNow()` can still trigger immediate wakes that get skipped if within the interval window.
<h3>Confidence Score: 5/5</h3>
- Safe to merge - minimal behavioral change with clear correctness improvement
- The fix is a simple, focused 1-line logic change that correctly addresses the root cause. The removal of the `isInterval` condition ensures all wake reasons respect the interval guard, preventing runaway loops. All 38 existing heartbeat tests pass, and the change aligns with the documented intent that `requestHeartbeatNow()` wakes the runner but respects the `nextDueMs` guard.
- No files require special attention
<sub>Last reviewed commit: f0cacca</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
90.5%
#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
by deggertsen · 2026-02-17
83.5%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
82.4%
#15193: fix(heartbeat): keep scheduler chain alive on requests-in-flight skip
by seilk · 2026-02-13
81.4%
#11752: fix(heartbeat): clamp setTimeout delay to 2^31-1 to prevent Timeout...
by kjaylee · 2026-02-08
77.9%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
76.4%
#11657: fix(cron): treat skipped heartbeat as ok for one-shot jobs
by DukeDeSouth · 2026-02-08
76.3%
#14241: fix(heartbeat): propagate originating session key for exec event qu...
by aldoeliacim · 2026-02-11
76.1%
#22340: fix(heartbeat): drain system events after event-driven heartbeat run
by AIflow-Labs · 2026-02-21
75.6%
#9184: Fix: Heartbeat timer not resuming after macOS sleep/wake cycle
by vishaltandale00 · 2026-02-05
75.5%