← Back to PRs

#14241: fix(heartbeat): propagate originating session key for exec event queue lookup

by aldoeliacim open 2026-02-11 19:37 View on GitHub →
gateway agents
#### 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