#21682: fix(heartbeat): propagate sessionKey in exec/hooks to fix async context loss
gateway
agents
size: XS
Cluster:
Session Management Enhancements
📝 Summary
This PR is a clean re-implementation of the fix for asynchronous context loss. It addresses the issue where execution results and webhook wakes failed to reach users in non-main sessions (e.g., Discord threads, DMs, or Slack channels).
> Note: This replaces PR #11888. Due to significant refactors in the upstream main branch, the previous implementation became obsolete. This PR re-applies the fix against the current codebase structure.
🔍 The Problem
The root cause was that requestHeartbeatNow was frequently called without a sessionKey. In the current architecture, this caused the heartbeat system to:
Default to the Main Session, leaving the actual target session queue un-flushed.
Coalesce unrelated wake requests into a single generic 'default' wake, losing the specific session context required for directed routing.
🛠 Changes
1. Agents & Tools (agents/)
bash-tools.exec-runtime.ts: Modified emitExecSystemEvent and maybeNotifyOnExit to strictly propagate the sessionKey when requesting a heartbeat after command completion or approval.
2. Gateway (gateway/)
server-node-events.ts: Updated remote node event handlers (exec.started, exec.finished, exec.denied) to carry the originating sessionKey back to the heartbeat system.
server/hooks.ts: Enhanced dispatchWakeHook to include the target sessionKey extracted from the wake request payload.
3. Testing (test/)
server-node-events.test.ts: Updated test assertions to strictly verify that the sessionKey is correctly passed to the heartbeat mock.
Regression Check: Verified that the "ghosting" issue in Discord is resolved by ensuring the wake-up signal is routed to the specific channel session.
🤖 AI/Vibe-Coding Transparency
AI-Assisted: Yes Opencode (Gemini 3.1 Pro).
Review: I have manually verified the propagation path from the tool execution layer to the gateway's event dispatcher.
🧪 Testing (Fully Verified)
Manual Test: Confirmed in Discord that model responses now correctly appear in the specific channel/thread after an exec tool approval.
Build & Lint: pnpm build && pnpm check (Fixed formatting in heartbeat-runner.ts). Status: Pass.
Unit Tests: pnpm test. Status: Pass. (Added regression tests for coalescing).
💡 Why this matters
This ensures the feedback loop for async actions is session-aware. Users in specialized sessions (Discord/Slack) will no longer be "ghosted" by the bot after long-running tasks or manual approvals.
Fixes #14191
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes async context loss in exec operations and webhook wakes by propagating `sessionKey` to the heartbeat system. The changes ensure that execution results and webhook notifications reach the correct session (Discord threads/DMs, Slack channels) instead of defaulting to the main session.
The implementation adds `sessionKey` to four `requestHeartbeatNow` call sites:
- `bash-tools.exec-runtime.ts`: propagates sessionKey on exec completion and exit notifications
- `server-node-events.ts`: includes sessionKey from remote node exec events (started/finished/denied)
- `server/hooks.ts`: passes sessionKey from wake hook dispatcher
Test coverage is comprehensive, with all test assertions updated to verify sessionKey propagation.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with minimal risk - it's a targeted fix that adds proper context propagation without changing core logic
- The changes are focused, well-tested, and address a specific bug. The implementation correctly propagates sessionKey through the call chain, and tests verify the behavior. Score reduced from 5 due to one observation: `dispatchAgentHook` in `hooks.ts` has two `requestHeartbeatNow` calls (lines 93, 101) that weren't updated with sessionKey. While events go to mainSessionKey, those heartbeats might also benefit from explicit sessionKey for consistency - though this may be intentional behavior for agent hooks vs wake hooks.
- `src/gateway/server/hooks.ts` - verify whether `dispatchAgentHook` heartbeats on lines 93 and 101 should also include mainSessionKey
<sub>Last reviewed commit: f96b4f3</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
#14241: fix(heartbeat): propagate originating session key for exec event qu...
by aldoeliacim · 2026-02-11
87.4%
#19270: fix: retry event-driven heartbeats blocked by requests-in-flight
by deggertsen · 2026-02-17
79.3%
#7301: fix(hooks): use resolveAgentIdFromSessionKey instead of split(":")[0]
by tsukhani · 2026-02-02
77.4%
#22277: fix: prevent heartbeat model override from bleeding into main session
by zhangjunmengyang · 2026-02-21
77.3%
#10912: feat: add --session-key to clawdis system event
by SocialNerd42069 · 2026-02-07
77.1%
#23759: fix: prevent heartbeat/internal providers from corrupting session l...
by kami-saia · 2026-02-22
76.8%
#6850: fix: support direct channel:account:peer format in session key extr...
by toboto · 2026-02-02
76.5%
#23525: fix: include sessionKey in session_start/session_end hook context
by p697 · 2026-02-22
76.4%
#4567: Webchat: canonicalize main session key for /new (fix #4446)
by selfboot · 2026-01-30
76.3%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
76.3%