Skip to content

[Bug]: Heartbeat set to reply to Telegram group gets mixed up with direct chat #28639

@jtheodas

Description

@jtheodas

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:

  1. 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

  1. 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

  1. 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;
}

  1. Clear Routing After Heartbeat

After sending a heartbeat, explicitly clear any routing state that might leak.

await sendHeartbeat(heartbeatTarget, heartbeatText);
session.deliveryContext = null; // Reset

  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions