#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
size: S
Cluster:
Error Payload Filtering
Closes #19302
## Problem
`resolveHeartbeatReplyPayload` iterates the reply payload array **backwards** and returns the last non-empty payload. When `buildFinalReplyPayloads` appends an `isError: true` tool error summary after the agent's `HEARTBEAT_OK` text, the error payload shadows the valid response and gets delivered to the channel as a spurious alert.
**Sequence:**
1. Heartbeat fires → agent processes it
2. Agent calls a tool (e.g. `exec`) that fails
3. Agent responds with `HEARTBEAT_OK`
4. `buildFinalReplyPayloads` appends `{ text: "⚠️ 🛠️ Exec: ...", isError: true }`
5. `resolveHeartbeatReplyPayload` iterates backwards → picks the error payload (last in array) over `HEARTBEAT_OK`
6. Error is delivered to channel instead of being suppressed
## Fix
Skip payloads with `isError: true` during backward iteration in `resolveHeartbeatReplyPayload`. Error payloads are tool diagnostic summaries that should never be selected for heartbeat delivery. When all non-error payloads are empty/absent, the function returns `undefined`, which triggers normal `HEARTBEAT_OK` suppression in the caller.
## Test plan
- [x] 10 new unit tests covering:
- Normal payload selection (single, array, empty)
- `isError` payloads skipped in favor of earlier valid payload
- Multiple trailing errors skipped
- All-error array returns `undefined`
- Media payloads preserved when errors are skipped
- Single (non-array) error payload passes through unchanged
- [x] `pnpm format:check` passes
- [x] `pnpm tsgo` passes
- [x] `pnpm lint` passes
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a bug where `resolveHeartbeatReplyPayload` could return an `isError: true` tool error summary instead of the agent's `HEARTBEAT_OK` text, causing spurious error messages to be delivered to the channel during heartbeat runs.
**Changes:**
- `src/auto-reply/heartbeat-reply-payload.ts`: Adds a 4-line guard in the backward iteration loop to skip payloads with `isError: true`, so tool error summaries (appended after the agent's response by `buildEmbeddedRunPayloads`) can never shadow a valid `HEARTBEAT_OK` reply. The fix is minimal, correctly placed before the content check, and well-commented.
- `src/auto-reply/heartbeat-reply-payload.test.ts`: New test file with 10 regression tests covering the primary fix scenario plus boundary cases (empty array, all-error array, media payloads, single non-array passthrough).
The fix is straightforward and correctly scoped. Both the source comment and the test JSDoc reference `buildFinalReplyPayloads` as the source of the error payloads, but this function does not exist in the codebase — the actual culprit is `buildEmbeddedRunPayloads` in `src/agents/pi-embedded-runner/run/payloads.ts`.
<h3>Confidence Score: 5/5</h3>
- Safe to merge — the fix is minimal, targeted, and well-tested with no risk of regression.
- The change is a 4-line guard in a single function, directly addresses the documented bug, is backed by 10 new unit tests, and doesn't affect any non-heartbeat code paths. The only findings are cosmetic comment inaccuracies (stale function name).
- No files require special attention.
<sub>Last reviewed commit: c2cee78</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19339: fix(heartbeat): skip isError payloads when resolving heartbeat reply
by aldoeliacim · 2026-02-17
93.7%
#19387: Fix #19302: Filter isError payloads before heartbeat selection
by cedillarack · 2026-02-17
87.5%
#16321: Fix #12767: suppress HEARTBEAT_OK leakage in Telegram DM replies
by tdjackey · 2026-02-14
82.2%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
81.3%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
80.4%
#17371: fix(heartbeat): always strip HEARTBEAT_OK token from reply text
by BinHPdev · 2026-02-15
80.4%
#21454: fix(cron): skip isError payloads when picking summary/delivery content
by Diaspar4u · 2026-02-19
80.4%
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
79.9%
#17950: fix: filter error payloads from user-facing messages
by Suksham-sharma · 2026-02-16
79.7%
#16373: fix: suppress leaked heartbeat poll prompts in reply delivery
by luisecab · 2026-02-14
79.5%