#12786: fix: drop heartbeat runs that arrive while another run is active
size: S
trusted-contributor
experienced-contributor
Cluster:
Heartbeat Management Improvements
## Summary
Fixes #8063
When a heartbeat run arrives while another agent run is already active for the same session, it was being enqueued as a followup. When the followup queue later drained, the stale heartbeat produced a duplicate agent run — sending extra response branches to the user.
This change adds an early-return guard in `runReplyAgent` that silently drops heartbeat runs when `isActive` is true, before they reach the enqueue path. The next heartbeat interval will independently re-check the session.
- Non-heartbeat runs are unaffected — they continue to enqueue normally
- The followup drain mechanism remains fully intact for legitimate queued messages
- Heartbeat runs that arrive when no other run is active execute normally
## Test plan
- [x] New test: heartbeat run is dropped (returns `undefined`, no enqueue) when `isActive: true`
- [x] New test: non-heartbeat run still enqueues when `isActive: true`
- [x] New test: heartbeat run executes normally when `isActive: false`
- [x] All 3 new tests fail before the fix, pass after (TDD)
- [x] Existing agent-runner test suites pass (13 test files)
- [x] Existing heartbeat-runner test suites pass (43 tests)
- [x] `pnpm build && pnpm check` pass
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This change adds a guard in `src/auto-reply/reply/agent-runner.ts` to silently drop heartbeat-triggered runs when another run is already active for the same session, preventing stale heartbeat followups from being enqueued and later producing duplicate agent runs/extra response branches (fix for #8063). It also adds a focused vitest suite that asserts: (1) heartbeat+active returns `undefined` and does not enqueue, (2) non-heartbeat+active still enqueues, and (3) heartbeat+inactive proceeds to execute normally.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- The change is narrowly scoped (an early-return guard for a specific heartbeat+active condition) and is covered by new targeted tests that validate both the new behavior and non-regression for non-heartbeat runs. The previously reported run-completion bookkeeping gap on the early-return path is addressed in the current diff via explicit `typing.markRunComplete()` calls.
- No files require special attention
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#13574: fix(heartbeat): skip when session has active run
by 1kuna · 2026-02-10
82.7%
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
82.7%
#17801: fix(heartbeat): enforce interval guard for non-interval wake reasons
by aldoeliacim · 2026-02-16
82.4%
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
81.3%
#15193: fix(heartbeat): keep scheduler chain alive on requests-in-flight skip
by seilk · 2026-02-13
81.0%
#21615: fix(tui): preserve main session model during heartbeat model override
by lailoo · 2026-02-20
80.8%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
80.1%
#22340: fix(heartbeat): drain system events after event-driven heartbeat run
by AIflow-Labs · 2026-02-21
80.1%
#15575: fix(heartbeat): suppress prefixed HEARTBEAT_OK ack replies (#15505)
by TsekaLuk · 2026-02-13
80.0%
#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
by deggertsen · 2026-02-17
79.8%