#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
size: XS
Cluster:
Heartbeat Management Improvements
## Summary
When exec completion, cron, wake, or hook events trigger a heartbeat but the main command lane is busy, the heartbeat was skipped with `requests-in-flight`. The system event text sat in context unprocessed until the next user message or scheduled heartbeat.
## The Fix
Exempt event-driven heartbeat reasons (`exec:*`, `cron:*`, `wake`, `hook:*`) from the `requests-in-flight` skip. The heartbeat enqueues a turn in the command lane, which naturally executes after the current one finishes. No retry loops or timers needed.
Also fixes a secondary bug: the notify-on-exit handler uses reason format `exec:<id>:exit` but the heartbeat runner only matched exact string `"exec-event"` — so exec completions weren't recognized as exec events for the specialized prompt path either. Now matches both via `startsWith('exec:')`.
## Changes
- `src/infra/heartbeat-runner.ts`: Move event-driven reason detection above queue-size check; add `!isEventDriven` exemption; consolidate `isEventDriven` flag for reuse in empty-heartbeat-file check; fix exec reason matching to include `exec:*` prefix.
- `src/infra/heartbeat-runner.scheduler.test.ts`: Test that event-driven wake requests are dispatched to runOnce.
**2 files changed, +42 −10**
## Related
- #8997 — exec completion notifications and agent turn interaction
- #14527 — system events silently skipped
- [Community report](https://www.answeroverflow.com/m/1469119489009254589) — agents not responding to exec completion events
## Reproduction
1. Start a background exec task (`background: true`)
2. Chat with the agent while the background task runs
3. Let the task finish while agent is mid-turn
4. **Before:** System event in context, agent never responds
5. **After:** Heartbeat goes through, agent processes the event after current turn
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes event-driven heartbeats (exec completions, cron events, wake, hooks) being silently skipped when the main command lane is busy (`requests-in-flight`). Previously, the system event text would sit unprocessed in context until the next scheduled heartbeat or user message.
- Moves event-driven reason detection (`exec:*`, `cron:*`, `wake`, `hook:*`) above the queue-size check and exempts these reasons from the `requests-in-flight` skip
- Fixes a secondary bug: exec reason matching now recognizes both the legacy `"exec-event"` format (from `server-node-events.ts`) and the `exec:<id>:exit` format (from `bash-tools.exec-runtime.ts` notify-on-exit handler)
- Consolidates `isEventDriven` flag to also exempt event-driven reasons from the empty-heartbeat-file skip, replacing the previous three separate boolean checks
- Adds a scheduler-level test for the event-driven wake path
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it fixes a clear bug with a minimal, well-scoped change and includes a test.
- The logic change is straightforward and correct: event-driven heartbeat reasons are exempted from the requests-in-flight skip by detecting them earlier and adding a guard. The exec reason matching fix addresses a real inconsistency between two callsites. The only minor gap is that isActionWakeReason in heartbeat-wake.ts was not updated for exec:* prefixes (pre-existing, low practical impact).
- No files require special attention. Consider also updating isActionWakeReason in src/infra/heartbeat-wake.ts for consistency.
<sub>Last reviewed commit: 716a503</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
84.6%
#15193: fix(heartbeat): keep scheduler chain alive on requests-in-flight skip
by seilk · 2026-02-13
83.7%
#17801: fix(heartbeat): enforce interval guard for non-interval wake reasons
by aldoeliacim · 2026-02-16
83.5%
#14241: fix(heartbeat): propagate originating session key for exec event qu...
by aldoeliacim · 2026-02-11
83.3%
#22340: fix(heartbeat): drain system events after event-driven heartbeat run
by AIflow-Labs · 2026-02-21
80.4%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
80.4%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
79.8%
#21851: fix: drain pending system events when main command lane goes idle
by alan-purring · 2026-02-20
79.5%
#21682: fix(heartbeat): propagate sessionKey in exec/hooks to fix async con...
by eviaaaaa · 2026-02-20
79.3%
#11657: fix(cron): treat skipped heartbeat as ok for one-shot jobs
by DukeDeSouth · 2026-02-08
77.8%