← Back to PRs

#23759: fix: prevent heartbeat/internal providers from corrupting session lastTo

by kami-saia open 2026-02-22 17:26 View on GitHub →
size: S
## Problem When a heartbeat or cron run fires, `resolveHeartbeatSenderId()` returns the literal string `"heartbeat"` as `ctx.To`. This gets persisted as `lastToRaw`, corrupting the session's reply target for subsequent channel messages. After a heartbeat run, any follow-up reply or cron announce delivery routes to `"heartbeat"` instead of the real channel (e.g. Discord, Telegram), causing silent delivery failures. **Reproduction:** Enable heartbeat on a multi-channel setup (Discord + cron). After the first heartbeat fires, cron announce deliveries and follow-up messages silently fail because the session's `lastTo` is now `"heartbeat"` instead of the real channel target. ## Fix - Add `INTERNAL_PROVIDER_NAMES` set (`heartbeat`, `cron-event`, `exec-event`, `isolated`, `system`) + `filterInternalChannel()` utility in `agent-runner-helpers.ts` - `session.ts`: skip `ctx.To` for `lastToRaw` when provider is internal; fall back to `ctx.OriginatingTo` or existing `lastTo` instead - `agent-runner.ts` + `followup-runner.ts`: filter `replyToChannel` through `filterInternalChannel()` so internal providers don't override routing ## Testing Tested in production over 48+ hours with 60-minute heartbeat interval on Discord + cron announce setup. No recurrence of `lastTo` corruption after the fix. Fixes #21235 <!-- greptile_comment --> <h3>Greptile Summary</h3> Prevents heartbeat and internal event providers (`heartbeat`, `cron-event`, `exec-event`, `isolated`, `system`) from corrupting session routing by introducing `INTERNAL_PROVIDER_NAMES` set and `filterInternalChannel()` utility. When internal providers fire, their synthetic channel values are now filtered out so they don't overwrite the session's real `lastTo` and `replyToChannel`, which previously caused silent delivery failures. - Adds centralized `INTERNAL_PROVIDER_NAMES` set and filtering utility - Updates `session.ts` to skip `ctx.To` for internal providers when persisting `lastToRaw` - Applies filtering to `replyToChannel` in both `agent-runner.ts` and `followup-runner.ts` - One minor issue: `filterInternalChannel()` should lowercase the input for case-insensitive matching <h3>Confidence Score: 4/5</h3> - Safe to merge with minor case-sensitivity improvement recommended - The fix correctly addresses the root cause by preventing internal synthetic providers from corrupting session routing. The implementation is well-documented and consistently applied across all relevant files. However, the case-sensitivity issue in `filterInternalChannel()` could theoretically allow bypass if `OriginatingChannel` is set with different casing, though this appears unlikely in practice given current codebase patterns. - Pay attention to `agent-runner-helpers.ts:34` for the case-sensitivity fix <sub>Last reviewed commit: f5d2beb</sub> <!-- greptile_other_comments_section --> <sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub> <!-- /greptile_comment -->

Most Similar PRs