#14241: fix(heartbeat): propagate originating session key for exec event queue lookup
gateway
agents
Cluster:
Session Management Enhancements
#### Summary
Fixes #14191. When an exec completes on a channel-specific session (Discord, WhatsApp, Slack, etc.), the heartbeat fires but fails to find the pending exec event because it peeks the main session queue instead of the originating session's queue.
lobster-biscuit
#### Root Cause
`requestHeartbeatNow()` discarded the originating session key. The system event was enqueued under the peer-specific session key (e.g. `agent:main:discord:channel:<id>`), but the heartbeat runner always checked `peekSystemEvents()` with the resolved heartbeat session key (`agent:main:main`).
#### Behavior Changes
- `requestHeartbeatNow()` now accepts an optional `sessionKey` parameter
- The wake handler propagates `sessionKey` through to `runHeartbeatOnce()`
- `runHeartbeatOnce()` uses the originating session key (when provided) for `peekSystemEvents()` instead of the resolved heartbeat session key
- No change to behavior when `sessionKey` is not provided (backward compatible)
#### Codebase and GitHub Search
- Searched all callers of `requestHeartbeatNow` — updated the three that enqueue exec events (`server-node-events.ts`, `bash-tools.exec.ts` x2)
- Confirmed `peekSystemEvents` is keyed per-session in `system-events.ts`
- Verified no other consumers depend on the previous signature shape
#### Tests
- All 67 existing heartbeat tests pass (`pnpm test -- --run src/infra/heartbeat`)
- `pnpm check` (format + typecheck + lint) passes clean
#### Files Changed
| File | Change |
|------|--------|
| `src/infra/heartbeat-wake.ts` | Add `sessionKey` to `HeartbeatWakeHandler` type, `requestHeartbeatNow()`, and propagation through scheduler |
| `src/infra/heartbeat-runner.ts` | Add `eventSessionKey` param to `runHeartbeatOnce()`, use it for `peekSystemEvents()` |
| `src/gateway/server-node-events.ts` | Pass `sessionKey` to `requestHeartbeatNow()` |
| `src/agents/bash-tools.exec.ts` | Pass `sessionKey` to `requestHeartbeatNow()` (2 call sites) |
**Sign-Off**
- Models used: Claude Opus 4
- Submitter effort: medium (traced full event flow across 4 files)
- Agent notes: Root cause was clearly documented in the issue; fix is minimal and backward-compatible
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
Propagates the originating session key through the heartbeat wake/run pipeline so that `peekSystemEvents()` checks the correct per-session queue when an exec event completes on a channel-specific session (Discord, WhatsApp, etc.).
- Adds optional `sessionKey` param to `requestHeartbeatNow()`, `HeartbeatWakeHandler`, and `runHeartbeatOnce()` — backward compatible, falls back to the resolved heartbeat session key when not provided.
- All three call sites that enqueue exec events (`server-node-events.ts`, `bash-tools.exec.ts` x2) now pass through `sessionKey`.
- **Bug in retry path**: The coalescing scheduler in `heartbeat-wake.ts` does not restore `pendingSessionKey` on retry (when "requests-in-flight" or error), causing the session key to be lost on the next attempt.
<h3>Confidence Score: 3/5</h3>
- Fix is directionally correct but has a bug in the retry path that can reproduce the original issue under load.
- The core approach is sound and the changes are minimal and backward-compatible. However, the retry branches in heartbeat-wake.ts do not restore pendingSessionKey, meaning the session key is silently dropped whenever the main lane is busy — a likely scenario under real-world load. This undermines the fix for the exact scenario described in the issue.
- `src/infra/heartbeat-wake.ts` — retry branches at lines 48-56 need to restore `pendingSessionKey` alongside `pendingReason`.
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21682: fix(heartbeat): propagate sessionKey in exec/hooks to fix async con...
by eviaaaaa · 2026-02-20
87.4%
#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
by deggertsen · 2026-02-17
83.3%
#22277: fix: prevent heartbeat model override from bleeding into main session
by zhangjunmengyang · 2026-02-21
79.6%
#15193: fix(heartbeat): keep scheduler chain alive on requests-in-flight skip
by seilk · 2026-02-13
79.2%
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
77.8%
#23759: fix: prevent heartbeat/internal providers from corrupting session l...
by kami-saia · 2026-02-22
77.5%
#22340: fix(heartbeat): drain system events after event-driven heartbeat run
by AIflow-Labs · 2026-02-21
77.4%
#15575: fix(heartbeat): suppress prefixed HEARTBEAT_OK ack replies (#15505)
by TsekaLuk · 2026-02-13
76.6%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
76.6%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
76.5%