Bug Description
When a heartbeat handler runs tool calls (e.g. exec, cron list), the embedded agent outputs [TOOL_CALL]{tool: "exec", args: {...}}[/TOOL_CALL] blocks in its response. These leak into user-facing Telegram/Discord messages instead of being silently discarded.
Steps to Reproduce
- Have HEARTBEAT.md content that triggers an exec or other tool call
- Any inbound message or scheduled heartbeat poll fires
- The heartbeat embedded agent runs
- User receives a message containing
[TOOL_CALL]{...}[/TOOL_CALL] — raw agent tool-call output
Example
[TOOL_CALL]
{tool => "exec", args => {
--command "ls memory/..."
}}
[/TOOL_CALL]
Root Cause
In agent-runner-payloads.ts, the buildReplyPayloads function has two branches:
- Normal messages: HEARTBEAT_TOKEN stripping + full sanitization
isHeartbeat=true: payloads pass through without any text filtering
const sanitizedPayloads = params.isHeartbeat
? params.payloads // ← no stripping, no sanitization
: params.payloads.flatMap(...)
When the embedded agent executes a tool, the result is injected back into the model as an observation. The model then generates a response that may contain [TOOL_CALL] bracket-format blocks. Since no stripping happens in heartbeat mode, this text flows into replyPayload.text and gets delivered to the user.
Note: [TOOL_CALL] (bracket format) is distinct from <tool_call> (XML format). PR #42260 addresses the XML format but does not cover the bracket format.
Expected Behavior
Heartbeat tool executions should never produce user-visible [TOOL_CALL] blocks.
Related
Bug Description
When a heartbeat handler runs tool calls (e.g.
exec,cron list), the embedded agent outputs[TOOL_CALL]{tool: "exec", args: {...}}[/TOOL_CALL]blocks in its response. These leak into user-facing Telegram/Discord messages instead of being silently discarded.Steps to Reproduce
[TOOL_CALL]{...}[/TOOL_CALL]— raw agent tool-call outputExample
Root Cause
In
agent-runner-payloads.ts, thebuildReplyPayloadsfunction has two branches:isHeartbeat=true: payloads pass through without any text filteringWhen the embedded agent executes a tool, the result is injected back into the model as an observation. The model then generates a response that may contain
[TOOL_CALL]bracket-format blocks. Since no stripping happens in heartbeat mode, this text flows intoreplyPayload.textand gets delivered to the user.Note:
[TOOL_CALL](bracket format) is distinct from<tool_call>(XML format). PR #42260 addresses the XML format but does not cover the bracket format.Expected Behavior
Heartbeat tool executions should never produce user-visible
[TOOL_CALL]blocks.Related