#23759: fix: prevent heartbeat/internal providers from corrupting session lastTo
size: S
Cluster:
Session Management Enhancements
## 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
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
79.9%
#6522: fix(cron): deliver original message when agent response is heartbea...
by sidmohan0 · 2026-02-01
78.2%
#6850: fix: support direct channel:account:peer format in session key extr...
by toboto · 2026-02-02
77.7%
#14241: fix(heartbeat): propagate originating session key for exec event qu...
by aldoeliacim · 2026-02-11
77.5%
#17527: fix(gateway): allow WebChat to attach to main session regardless of...
by Glucksberg · 2026-02-15
76.9%
#21682: fix(heartbeat): propagate sessionKey in exec/hooks to fix async con...
by eviaaaaa · 2026-02-20
76.8%
#15982: fix: pass agentId to resolveSessionFilePath in reply flow (NX-003)
by automagik-genie · 2026-02-14
76.7%
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
76.3%
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
76.3%
#13524: feat: conditional bootstrap file loading for heartbeat vs DM sessions
by tarun131313 · 2026-02-10
76.2%