#20948: fix: propagate accountId from heartbeat delivery context to agent run
size: XS
## Problem
When a heartbeat fires for a non-default agent (e.g. one bound to a specific Telegram account like `accountId: "leon"`), the heartbeat runner correctly resolves `delivery.accountId` from the agent's heartbeat config. However, this `accountId` was never passed into the `ctx` (MsgContext) object that feeds into `getReplyFromConfig()`.
This caused the agent's `message` tool to be created without a default `agentAccountId`, so any outbound messages sent during the heartbeat run fell back to the `default` channel account — which may be a completely different bot that the target user has never interacted with.
**Symptom:** `Telegram send failed: chat not found` when a heartbeat-triggered agent tries to message a user who only knows the agent's specific bot.
## Root Cause
In `heartbeat-runner.ts`, the context object was missing `AccountId`:
```ts
const ctx = {
Body: ...,
From: sender,
To: sender,
Provider: "heartbeat",
SessionKey: sessionKey,
// AccountId was missing here!
};
```
The `AccountId` flows through:
```
ctx.AccountId → getReplyFromConfig → get-reply-run.ts (sessionCtx.AccountId)
→ agentAccountId → createMessageTool() → message-tool.ts fallback
```
Without it, `agentAccountId` resolves to `undefined`, and the message tool uses the channel's default account.
## Fix
One-line addition: `AccountId: delivery.accountId` in the heartbeat `ctx` object.
## When This Matters
- Multiple accounts configured for a channel (e.g. `default` + `leon` for Telegram)
- A non-default agent has heartbeat enabled with explicit `accountId`
- The agent sends messages during heartbeat runs (check-ins, proactive alerts, etc.)
Regular inbound message replies are unaffected because the incoming message carries the account context naturally.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a bug where heartbeat-triggered agents with non-default account configurations (e.g., `accountId: "leon"` for a specific Telegram bot) would fail to send messages during heartbeat runs. The fix adds the missing `AccountId: delivery.accountId` field to the context object created in `heartbeat-runner.ts:671`.
The `accountId` flows through the following path:
- `ctx.AccountId` → `getReplyFromConfig()` → `sessionCtx.AccountId` (get-reply-run.ts:403)
- → `agentAccountId` → `createMessageTool()` → message-tool.ts fallback logic
Without this field, the message tool would fall back to the channel's `default` account, causing "chat not found" errors when the target user had only interacted with the specific non-default bot.
The fix is verified by:
- Existing tests in `heartbeat-runner.returns-default-unset.test.ts` that validate `accountId` propagation
- Consistent pattern with other message handlers (Discord, Telegram, etc.) which all include `AccountId` in their context objects
- The `delivery.accountId` is already populated by `resolveHeartbeatDeliveryTarget()` and used elsewhere in the same function (lines 872, 888)
This is a targeted, one-line fix that aligns heartbeat context with the established pattern used throughout the codebase.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with no risk
- The fix is a simple, one-line addition that adds a missing field to an existing context object. The change is well-tested through existing test coverage, follows established patterns throughout the codebase, and addresses a clear bug with a precise solution. The `delivery.accountId` value is already validated and used elsewhere in the same function, so there are no edge cases or risks introduced.
- No files require special attention
<sub>Last reviewed commit: 0394cd2</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#8507: fix: preserve accountId for multi-account agent-to-agent messaging
by djh58 · 2026-02-04
81.4%
#8357: fix(gateway): preserve accountId across gateway restarts
by alfredo-feat-volky · 2026-02-03
77.5%
#8095: fix(sessions): include accountId in deliveryContext for outbound pe...
by codeslayer44 · 2026-02-03
75.5%
#16259: fix(cron): pass accountId through delivery path for multi-account c...
by ComputClaw · 2026-02-14
75.2%
#23759: fix: prevent heartbeat/internal providers from corrupting session l...
by kami-saia · 2026-02-22
74.8%
#17583: feat(heartbeat): add agentId to route default heartbeat to a specif...
by scottgl9 · 2026-02-15
74.6%
#16321: Fix #12767: suppress HEARTBEAT_OK leakage in Telegram DM replies
by tdjackey · 2026-02-14
74.6%
#7350: fix(cron): pass agentId and AccountId through heartbeat chain for m...
by codeslayer44 · 2026-02-02
74.5%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
74.5%
#6007: fix(cron): auto-map agentId to accountId for Discord deliveries
by dwfinkelstein · 2026-02-01
74.4%