← Back to PRs

#11859: fix: filter HEARTBEAT_OK messages from chat.history when showOk is false

by Zjianru open 2026-02-08 12:52 View on GitHub →
app: web-ui gateway stale
## Summary Fixes #11630. `chat.history` returns raw transcript messages without heartbeat filtering, causing HEARTBEAT_OK messages to appear in the control-ui chat tab after page refresh — even though the live broadcast path correctly suppresses them via `shouldSuppressHeartbeatBroadcast()`. ## Changes ### `src/gateway/chat-sanitize.ts` - Added `filterHeartbeatOkMessages()` that identifies and removes HEARTBEAT_OK response messages and their corresponding heartbeat prompt messages from the message array - Detection uses content-based matching (checks for `HEARTBEAT_TOKEN` in assistant messages and `HEARTBEAT_PROMPT` in user messages), consistent with how `stripHeartbeatToken()` in `heartbeat.ts` handles markup normalization - Heartbeat runs that produced actual content (alerts) are preserved ### `src/gateway/server-methods/chat.ts` - After sanitization, applies `filterHeartbeatOkMessages()` when `resolveHeartbeatVisibility()` returns `showOk: false` for the webchat channel - When `showOk` is true, no filtering is applied (all messages returned as before) ### `src/gateway/chat-sanitize.test.ts` - 8 new test cases covering: prompt+response pair removal, standalone HEARTBEAT_OK removal, alert preservation, markdown markup wrapping, content array format, identity reference preservation, empty input, and multiple heartbeat pairs ## Design Notes - The heartbeat prompt and HEARTBEAT_OK response are identified by content matching rather than metadata flags, since `isHeartbeat` is a runtime `AgentRunContext` property that is not persisted to the JSONL transcript - The markup stripping in `isHeartbeatOkResponse()` mirrors the edge-stripping approach in `heartbeat.ts` (`stripHeartbeatToken`), removing `*`, backtick, `~` wrappers and HTML tags while preserving underscores in the token itself - Filtering is applied after `stripEnvelopeFromMessages()` but before `capArrayByJsonBytes()`, so byte budget calculation uses the filtered (smaller) set ## Testing ``` ✓ src/gateway/chat-sanitize.test.ts (12 tests) 4ms ✓ src/infra/heartbeat-visibility.test.ts (13 tests) 3ms ✓ src/auto-reply/heartbeat.test.ts (20 tests) 4ms ``` --- 感谢 @AnthonyFrancis 提交了清晰的 bug report! Thanks @AnthonyFrancis for the clear bug report! <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adds transcript-side filtering of heartbeat noise in `chat.history`: after stripping envelope headers, it optionally removes HEARTBEAT prompt/HEARTBEAT_OK pairs when heartbeat visibility resolves to `showOk: false`, and introduces unit tests around the new filter. The change plugs a gap where live broadcast already suppressed HEARTBEAT_OK but history replay returned raw transcript messages, causing the control-ui chat tab to show HEARTBEAT_OK on refresh. <h3>Confidence Score: 3/5</h3> - Moderately safe, but two logic issues can cause the bug to persist or change behavior for non-webchat consumers. - Core idea and tests look reasonable, but HEARTBEAT_OK detection diverges from existing markup normalization (misses underscore emphasis), and `chat.history` now applies webchat heartbeat visibility unconditionally which can unintentionally filter history for other clients/tools. - src/gateway/chat-sanitize.ts and src/gateway/server-methods/chat.ts <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs