← Back to PRs

#16373: fix: suppress leaked heartbeat poll prompts in reply delivery

by luisecab open 2026-02-14 18:00 View on GitHub →
size: S
## Problem When the agent is busy and a heartbeat poll prompt leaks into the reply pipeline, the raw heartbeat prompt text can be delivered to users. The original suppression logic only matched hardcoded/default phrasing, so custom prompts from `config.agents.defaults.heartbeat.prompt` could bypass filtering. Additionally, after `normalizeReplyPayload` gained `heartbeatPrompt`, production callers were not wiring it through, so custom prompts were still not consistently applied in real delivery paths. ## Solution Make heartbeat-poll leak suppression config-aware in `normalizeReplyPayload` and wire the resolved prompt through production call paths. - Added `heartbeatPrompt?: string` to `NormalizeReplyOptions` - Resolve prompt via `resolveHeartbeatPrompt(opts.heartbeatPrompt)` - Compare using lowercase + whitespace-collapsed full-text matching - Suppress repeated/stacked prompt leaks - Preserve media/channel-data payloads - Threaded heartbeat prompt through dispatcher path via `createReplyPrefixOptions` -> `ReplyDispatcherOptions` -> `normalizeReplyPayload` - Wired direct `routeReply` normalization call to pass resolved heartbeat prompt from config ## Files changed - `src/auto-reply/reply/normalize-reply.ts` - `src/auto-reply/reply/reply-utils.test.ts` - `src/channels/reply-prefix.ts` - `src/auto-reply/reply/reply-dispatcher.ts` - `src/auto-reply/reply/route-reply.ts` ## Validation - `./node_modules/.bin/tsc --noEmit` ## Notes - Rebased on latest `main` - Tests were moved from deleted `normalize-reply.test.ts` into current consolidated `reply-utils.test.ts` <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR fixes heartbeat poll prompt leaks by making suppression config-aware. Previously, only hardcoded default prompts were detected and filtered; custom prompts from `config.agents.defaults.heartbeat.prompt` would leak through to users. The fix threads the resolved heartbeat prompt through two main delivery paths: - **Dispatcher path**: `createReplyPrefixOptions` → `ReplyDispatcherOptions` → `normalizeReplyPayload` - **Direct routing**: `routeReply` resolves prompt from config and passes to `normalizeReplyPayload` The suppression logic uses case-insensitive, whitespace-collapsed comparison and handles stacked/repeated prompts. Payloads with media or channel data are preserved even if text matches the prompt. Prompts with additional content (e.g. "PROMPT Thanks, done.") are correctly treated as real replies. Test coverage is comprehensive, including default prompts, custom prompts, stacked prompts, prompts with extra content, legitimate messages mentioning heartbeat, and media preservation. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The implementation is straightforward, well-tested, and addresses a real bug. The heartbeat prompt is threaded through all normalization paths correctly. Type safety ensures all callers must provide the prompt. The logic correctly handles edge cases (stacked prompts, prompts with extra content, media preservation). Previous review concerns have been addressed with clarifying comments and regression tests. No breaking changes or risky refactoring. - No files require special attention <sub>Last reviewed commit: ee23af8</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs