#15575: fix(heartbeat): suppress prefixed HEARTBEAT_OK ack replies (#15505)
gateway
stale
size: S
Cluster:
HEARTBEAT_OK Suppression Fixes
## Summary
Prevents heartbeat ack leakage when the model returns `responsePrefix + HEARTBEAT_OK` (for example, `[helper] HEARTBEAT_OK`).
Closes #15505.
## Problem
Heartbeat token filtering only stripped edge-positioned `HEARTBEAT_OK`. If a reply already contained a configured `responsePrefix`, token stripping could miss it and send a visible ack message.
## Root Cause
`normalizeHeartbeatReply()` ran `stripHeartbeatToken()` directly on raw payload text, without accounting for a leading configured response prefix.
## Changes
- `src/infra/heartbeat-runner.ts`
- In `normalizeHeartbeatReply()`, strip a leading configured `responsePrefix` before heartbeat token detection.
- Preserve existing behavior for non-token replies (prefix is still reapplied for actual alert text).
- `src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts`
- Added regression test: `[helper] HEARTBEAT_OK` is treated as ack noise and not delivered.
## Tests
- `pnpm vitest run src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts src/infra/heartbeat-runner.returns-default-unset.test.ts`
- Passed (43/43).
## Sign-Off
lobster-biscuit
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR updates heartbeat reply normalization to treat `responsePrefix + HEARTBEAT_OK` (e.g. `[helper] HEARTBEAT_OK`) as heartbeat ack noise by stripping a leading configured `responsePrefix` before running `stripHeartbeatToken()`. It also adds a regression test ensuring prefixed heartbeat acks are not delivered.
The change fits into the existing heartbeat flow by adjusting `normalizeHeartbeatReply()` (used by `runHeartbeatOnce`) so token detection works even when the model prepends the configured response prefix, preventing visible ack leakage to outbound channels.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge after addressing a small prefix normalization inconsistency.
- Core logic change is localized and covered by a regression test; the main remaining concern is inconsistent handling of whitespace in `responsePrefix` between detection and reapplication which can lead to duplicated/odd prefixes in delivered messages for configs with stray whitespace.
- src/infra/heartbeat-runner.ts
<sub>Last reviewed commit: f9e9150</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#17371: fix(heartbeat): always strip HEARTBEAT_OK token from reply text
by BinHPdev · 2026-02-15
85.1%
#23588: fix(auto-reply): suppress repetitive HEARTBEAT_OK loops
by mohandshamada · 2026-02-22
83.3%
#16321: Fix #12767: suppress HEARTBEAT_OK leakage in Telegram DM replies
by tdjackey · 2026-02-14
82.3%
#22277: fix: prevent heartbeat model override from bleeding into main session
by zhangjunmengyang · 2026-02-21
81.9%
#16373: fix: suppress leaked heartbeat poll prompts in reply delivery
by luisecab · 2026-02-14
80.6%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
80.0%
#21615: fix(tui): preserve main session model during heartbeat model override
by lailoo · 2026-02-20
79.4%
#19387: Fix #19302: Filter isError payloads before heartbeat selection
by cedillarack · 2026-02-17
79.2%
#11859: fix: filter HEARTBEAT_OK messages from chat.history when showOk is ...
by Zjianru · 2026-02-08
79.0%
#9429: fix: skip session model override for heartbeat runs
by dbottme · 2026-02-05
78.9%