Summary
Heartbeat is set to reply to a Telegram group.
A direct message to the agent was sent at the same time the hearbeat ran.
The heartbeat reply got sent to my direct chat, while the reply to my DM message got sent to the group chat.
Steps to reproduce
Have a heartbeat.md with instructions that require some thinking and processing.
Then direct message the agent at the same time the heartbeat is running.
I asked the agent to analyze the architectural cause of this bug and propose solutions.
Analysis of the Bug:
The session data reveals the issue:
• agent:main:main has deliveryContext.to: "telegram:xxxxxxxx" (your direct chat)
• lastHeartbeatText contains my response to your direct message
• Heartbeat config routes to "to": "-yyyyyyyyyyyy" (Stocks_Report group)
Root cause: When the heartbeat triggered during an active session, the heartbeat's routing target appears to have overwritten or conflicted with the session's delivery context.
───
Proposed Solutions:
- Per-Message Routing Isolation
Each outgoing message should use the routing from its triggering message, not from session state or other sources.
// Instead of:
deliveryContext.to = heartbeatTarget;
// Do:
message.to = messageTrigger.origin.to; // Use the message that triggered this response
- Heartbeat Session Separation
Run heartbeats in an isolated sub-session that doesn't share delivery context with the main session.
Current: agent:main:main handles both user messages AND heartbeats
Proposed: agent:main:heartbeat:* handles heartbeats separately
- Active Session Lock
If a user message is being processed, defer heartbeat processing until the session is idle.
if (session.isActive && session.lastMessageAge < 60000) {
// Skip heartbeat, user is active
return;
}
- Clear Routing After Heartbeat
After sending a heartbeat, explicitly clear any routing state that might leak.
await sendHeartbeat(heartbeatTarget, heartbeatText);
session.deliveryContext = null; // Reset
- Tag & Filter Heartbeat Messages
Mark heartbeat-generated content clearly, and prevent it from being sent as a user response.
// In session state
session.lastHeartbeatSent = now;
session.lastHeartbeatContent = response;
// Before sending user response
if (response === session.lastHeartbeatContent) {
// Don't send - this was already sent as heartbeat
return;
}
Expected behavior
Heartbeats reliably reply to the target configured in Openclaw.json and doesn't get mixed up with direct messages that user is exchanging with the agent.
Actual behavior
Replies get mixed up between 2 different channels.
OpenClaw version
2026.2.25
Operating system
Ubuntu 24.04.3 LTS
Install method
No response
Logs, screenshots, and evidence
Impact and severity
Affected
Additional information
No response
Summary
Heartbeat is set to reply to a Telegram group.
A direct message to the agent was sent at the same time the hearbeat ran.
The heartbeat reply got sent to my direct chat, while the reply to my DM message got sent to the group chat.
Steps to reproduce
Have a heartbeat.md with instructions that require some thinking and processing.
Then direct message the agent at the same time the heartbeat is running.
I asked the agent to analyze the architectural cause of this bug and propose solutions.
Analysis of the Bug:
The session data reveals the issue:
• agent:main:main has deliveryContext.to: "telegram:xxxxxxxx" (your direct chat)
• lastHeartbeatText contains my response to your direct message
• Heartbeat config routes to "to": "-yyyyyyyyyyyy" (Stocks_Report group)
Root cause: When the heartbeat triggered during an active session, the heartbeat's routing target appears to have overwritten or conflicted with the session's delivery context.
───
Proposed Solutions:
Each outgoing message should use the routing from its triggering message, not from session state or other sources.
// Instead of:
deliveryContext.to = heartbeatTarget;
// Do:
message.to = messageTrigger.origin.to; // Use the message that triggered this response
Run heartbeats in an isolated sub-session that doesn't share delivery context with the main session.
Current: agent:main:main handles both user messages AND heartbeats
Proposed: agent:main:heartbeat:* handles heartbeats separately
If a user message is being processed, defer heartbeat processing until the session is idle.
if (session.isActive && session.lastMessageAge < 60000) {
// Skip heartbeat, user is active
return;
}
After sending a heartbeat, explicitly clear any routing state that might leak.
await sendHeartbeat(heartbeatTarget, heartbeatText);
session.deliveryContext = null; // Reset
Mark heartbeat-generated content clearly, and prevent it from being sent as a user response.
// In session state
session.lastHeartbeatSent = now;
session.lastHeartbeatContent = response;
// Before sending user response
if (response === session.lastHeartbeatContent) {
// Don't send - this was already sent as heartbeat
return;
}
Expected behavior
Heartbeats reliably reply to the target configured in Openclaw.json and doesn't get mixed up with direct messages that user is exchanging with the agent.
Actual behavior
Replies get mixed up between 2 different channels.
OpenClaw version
2026.2.25
Operating system
Ubuntu 24.04.3 LTS
Install method
No response
Logs, screenshots, and evidence
Impact and severity
Affected
Additional information
No response