#19339: fix(heartbeat): skip isError payloads when resolving heartbeat reply
size: S
trusted-contributor
Cluster:
Error Payload Filtering
## Summary
Fixes #19302.
`resolveHeartbeatReplyPayload` iterates the reply payload array backwards and picks the last non-empty payload. When `buildFinalReplyPayloads` appends a tool error summary (`isError: true`) after the agent's `HEARTBEAT_OK` text, the error payload wins and gets delivered to the channel — causing spurious error alerts during heartbeats.
## Fix
Two-pass strategy in `resolveHeartbeatReplyPayload`:
1. **First pass** (backwards): skip `isError: true` payloads, prefer the agent's actual reply
2. **Fallback pass** (backwards): if no non-error payload exists, return the last error payload
This preserves existing behavior when there are no error payloads, and correctly suppresses tool error leaks when the agent has already responded with `HEARTBEAT_OK`.
## Tests
Added `heartbeat-reply-payload.test.ts` with 8 test cases covering:
- Single/array payloads
- Error-after-agent-text (the bug scenario)
- Error-only arrays (fallback behavior)
- Empty payloads
- Mixed ordering
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes spurious error alerts during heartbeats by implementing a two-pass payload selection strategy in `resolveHeartbeatReplyPayload`. When tool errors are appended after an agent's `HEARTBEAT_OK` response, the first pass now skips `isError: true` payloads to prefer the agent's actual reply. The fallback pass ensures error payloads are still returned when no non-error content exists.
- Refactored payload content detection into a reusable `hasContent` helper
- Added comprehensive test coverage with 8 test cases covering single/array payloads, error-after-text scenarios, error-only fallback, empty payloads, and mixed ordering
- Preserves backward compatibility for all existing scenarios while fixing the bug where tool error summaries leaked to channels
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The fix is well-designed with a clear two-pass strategy that preserves all existing behavior while fixing the specific bug. Comprehensive test coverage validates all edge cases including the bug scenario, fallback behavior, and mixed ordering. The implementation is clean, follows the codebase patterns, and doesn't introduce any breaking changes or side effects.
- No files require special attention
<sub>Last reviewed commit: 9308590</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
93.7%
#19387: Fix #19302: Filter isError payloads before heartbeat selection
by cedillarack · 2026-02-17
85.6%
#21454: fix(cron): skip isError payloads when picking summary/delivery content
by Diaspar4u · 2026-02-19
80.5%
#16321: Fix #12767: suppress HEARTBEAT_OK leakage in Telegram DM replies
by tdjackey · 2026-02-14
80.0%
#17950: fix: filter error payloads from user-facing messages
by Suksham-sharma · 2026-02-16
79.9%
#16373: fix: suppress leaked heartbeat poll prompts in reply delivery
by luisecab · 2026-02-14
79.7%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
79.4%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
78.6%
#15575: fix(heartbeat): suppress prefixed HEARTBEAT_OK ack replies (#15505)
by TsekaLuk · 2026-02-13
77.8%
#17371: fix(heartbeat): always strip HEARTBEAT_OK token from reply text
by BinHPdev · 2026-02-15
77.4%