#19745: fix(heartbeat): enforce interval check regardless of trigger source
size: S
Cluster:
Heartbeat Management Improvements
## Problem
Heartbeat polls were firing every few seconds instead of the configured interval (e.g., 30 minutes). This was reported in issue #14440.
## Root Cause
In `src/infra/heartbeat-runner.ts`, the interval check was:
```typescript
if (isInterval && now < agent.nextDueMs) {
```
This only enforced the interval when the trigger reason was `"interval"` (timer-based). When triggered by other events like exec completions, cron wakes, or subagent completions, `isInterval` was `false`, causing the check to be bypassed entirely.
## Fix
Remove the `isInterval &&` condition so the interval is always enforced:
```typescript
if (now < agent.nextDueMs) {
```
This ensures the heartbeat only fires when the configured interval has elapsed, regardless of what triggers the check.
## Testing
After applying this fix:
- `every: "30m"` config is now respected
- Exec completions no longer bypass the interval
- Heartbeats fire at the configured interval, not on every activity event
Fixes #14440
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a heartbeat scheduling bug where the interval check (`nextDueMs`) was only enforced for timer-based (`"interval"`) triggers. Non-interval triggers like exec completions and wake events bypassed the `isInterval &&` guard, causing heartbeats to fire on every activity event instead of at the configured interval.
The one-line fix removes `isInterval &&` from the condition in the broadcast agent loop, so `now < agent.nextDueMs` is always enforced regardless of the trigger reason.
- **Targeted wake requests are unaffected**: Triggers with `agentId`/`sessionKey` (e.g., cron jobs) use a separate code path (line 1025) that already bypasses this loop
- **Behavioral change for exec-events**: `exec-event` triggers from `server-node-events.ts` and `bash-tools.exec-runtime.ts` do not include `agentId`/`sessionKey`, so they enter the broadcast loop and will now be subject to the interval check — exec results may be delayed until the next scheduled heartbeat (explicitly noted as intended in the PR description)
- The `isInterval` variable on line 1020 is still used at line 1096 for the return reason, so it's not dead code
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — the fix is minimal, targeted, and addresses a clear bug with excessive heartbeat polling.
- Single-line change that correctly removes a faulty guard condition. The fix is well-motivated by the reported issue (#14440). Score is 4 rather than 5 because untargeted exec-event triggers will now be delayed until the next interval, which could impact user experience for async command results, and no tests were added for the specific scenario fixed.
- No files require special attention — the change is a single condition removal in `src/infra/heartbeat-runner.ts`.
<sub>Last reviewed commit: a793ae1</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#17801: fix(heartbeat): enforce interval guard for non-interval wake reasons
by aldoeliacim · 2026-02-16
90.5%
#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
by deggertsen · 2026-02-17
84.6%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
82.7%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
81.5%
#15193: fix(heartbeat): keep scheduler chain alive on requests-in-flight skip
by seilk · 2026-02-13
80.7%
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
79.9%
#12240: fix: suppress heartbeat agent events from webchat broadcast
by Yida-Dev · 2026-02-09
79.5%
#11752: fix(heartbeat): clamp setTimeout delay to 2^31-1 to prevent Timeout...
by kjaylee · 2026-02-08
79.5%
#19387: Fix #19302: Filter isError payloads before heartbeat selection
by cedillarack · 2026-02-17
78.1%
#14241: fix(heartbeat): propagate originating session key for exec event qu...
by aldoeliacim · 2026-02-11
77.8%