Bug: heartbeat exec-event relay prompt drops the actual system-event body and can emit generic English fallback text
Summary
When heartbeat wakes for an async exec completion event, it detects that an exec finished, but the heartbeat prompt only says:
An async command you ran earlier has completed...
It does not include the actual queued system-event text such as:
Exec completed (abcd1234, code 0) :: <compact output>
As a result, the model often receives only a completion cue without the real result body and can emit generic fallback text like:
I can’t see the completed command output from here...
This fallback can then be sent to the user.
There is also a second issue that amplifies the problem:
- heartbeat uses
peekSystemEventEntries(...) rather than consuming processed events
- so the same malformed exec-completion cue can be seen again on later heartbeat ticks
Environment
- OpenClaw:
2026.4.11 (769908e)
- Node.js:
v24.14.1
- OS:
Linux nanli-XPS-8930 6.17.0-20-generic x86_64 Ubuntu 24.04
- Affected observed path:
- isolated cron run completion
- heartbeat wake reason:
exec-event
- delivery channel: Telegram
Concrete reproduction
Observed with job:
- Name:
新闻原始抓取
- Job ID:
392e2cfd-abd2-40a2-8aa2-fc05937e372b
1. The isolated cron run itself is correct
Transcript:
~/.openclaw/agents/main/sessions/113b44cf-527c-4e2d-8195-9d1deae42c2a.jsonl
Final assistant text:
Run history:
~/.openclaw/cron/runs/392e2cfd-abd2-40a2-8aa2-fc05937e372b.jsonl
Relevant entry:
2026-04-14 14:05
sessionId: 113b44cf-527c-4e2d-8195-9d1deae42c2a
summary: news_fetcher ok
delivered: false
2. A later heartbeat turn receives only a generic async-completion prompt
Heartbeat transcript:
~/.openclaw/agents/main/sessions/918ac060-3a28-4924-ae48-362ec6b5af05.jsonl
The heartbeat turn receives this user message:
An async command you ran earlier has completed. The result is shown in the system messages above. Please relay the command output to the user in a helpful way. If the command succeeded, share the relevant output. If it failed, explain what went wrong.
But the heartbeat run does not include the real exec completion text in the prompt itself.
3. Heartbeat then emits generic fallback English
Same transcript final assistant text:
I can’t see the completed command output from here.
I checked the session state, and there are **no running or recent sessions**, so there’s nothing I can safely relay.
If you paste the output, I’ll summarize it cleanly.
This is then actually sent by heartbeat.
openclaw system heartbeat last --json showed:
status: sent
preview: "I can’t see the completed command output from here..."
Root cause
A. Heartbeat detects exec completion, but drops the real event text
File:
dist/heartbeat-runner-U2x6TbnN.js
Relevant logic:
const pendingEventEntries = peekSystemEventEntries(session.sessionKey);
const pendingEvents = ...pendingEventEntries.map((event) => event.text);
const hasExecCompletion = pendingEvents.some(isExecCompletionEvent);
...
prompt: hasExecCompletion
? buildExecEventPrompt({ deliverToUser: ... })
: ...
buildExecEventPrompt(...) only returns a generic template:
"An async command you ran earlier has completed. The result is shown in the system messages above..."
It does not include the actual pendingEvents content.
B. The actual exec completion event body does exist upstream
File:
dist/bash-tools.exec-runtime-jfecOhNR.js
Background exec completion enqueues:
enqueueSystemEvent(
output
? `Exec ${status} (${session.id.slice(0, 8)}, ${exitLabel}) :: ${output}`
: `Exec ${status} (${session.id.slice(0, 8)}, ${exitLabel})`,
...
);
So the useful text exists, but the heartbeat prompt builder throws it away.
C. Heartbeat only peeks events, it does not consume processed ones
File:
dist/heartbeat-runner-U2x6TbnN.js
dist/system-events-BwxCjOwe.js
Heartbeat uses:
peekSystemEventEntries(session.sessionKey)
System events are only cleared by:
drainSystemEventEntries(sessionKey)
but heartbeat does not drain processed exec events after successful handling.
That means the same bad exec-completion cue can survive into later heartbeat runs and repeat.
Expected behavior
For exec-event heartbeat handling:
- The heartbeat prompt should include the actual exec completion body, not only a generic cue.
- After heartbeat successfully handles the exec completion event, the processed event should be consumed so it does not repeat.
Actual behavior
- Heartbeat sees only a generic “async command completed” prompt.
- Model lacks the real result body.
- Model emits generic fallback English.
- Heartbeat may send that fallback to the user.
- Because the event is only peeked, the same problem can repeat on later ticks.
Impact
- User-visible English pollution in non-English chats
- Incorrect relay of async command completions
- Repeatable noise because processed events are not consumed
Minimal fix direction
- In heartbeat exec-event prompt construction, include the actual exec completion event text from
pendingEventEntries.
- After successful heartbeat handling, consume the processed exec events instead of leaving them queued.
Notes
This issue is distinct from the isolated cron session inheritance bug involving stale lastHeartbeatText carry-over.
That other bug affects session-store initialization.
This issue is about heartbeat exec-event prompt construction and event consumption.
Bug: heartbeat exec-event relay prompt drops the actual system-event body and can emit generic English fallback text
Summary
When heartbeat wakes for an async exec completion event, it detects that an exec finished, but the heartbeat prompt only says:
It does not include the actual queued system-event text such as:
As a result, the model often receives only a completion cue without the real result body and can emit generic fallback text like:
This fallback can then be sent to the user.
There is also a second issue that amplifies the problem:
peekSystemEventEntries(...)rather than consuming processed eventsEnvironment
2026.4.11 (769908e)v24.14.1Linux nanli-XPS-8930 6.17.0-20-generic x86_64 Ubuntu 24.04exec-eventConcrete reproduction
Observed with job:
新闻原始抓取392e2cfd-abd2-40a2-8aa2-fc05937e372b1. The isolated cron run itself is correct
Transcript:
~/.openclaw/agents/main/sessions/113b44cf-527c-4e2d-8195-9d1deae42c2a.jsonlFinal assistant text:
Run history:
~/.openclaw/cron/runs/392e2cfd-abd2-40a2-8aa2-fc05937e372b.jsonlRelevant entry:
2026-04-14 14:05sessionId: 113b44cf-527c-4e2d-8195-9d1deae42c2asummary: news_fetcher okdelivered: false2. A later heartbeat turn receives only a generic async-completion prompt
Heartbeat transcript:
~/.openclaw/agents/main/sessions/918ac060-3a28-4924-ae48-362ec6b5af05.jsonlThe heartbeat turn receives this user message:
But the heartbeat run does not include the real exec completion text in the prompt itself.
3. Heartbeat then emits generic fallback English
Same transcript final assistant text:
This is then actually sent by heartbeat.
openclaw system heartbeat last --jsonshowed:status: sentpreview: "I can’t see the completed command output from here..."Root cause
A. Heartbeat detects exec completion, but drops the real event text
File:
dist/heartbeat-runner-U2x6TbnN.jsRelevant logic:
buildExecEventPrompt(...)only returns a generic template:"An async command you ran earlier has completed. The result is shown in the system messages above..."It does not include the actual
pendingEventscontent.B. The actual exec completion event body does exist upstream
File:
dist/bash-tools.exec-runtime-jfecOhNR.jsBackground exec completion enqueues:
So the useful text exists, but the heartbeat prompt builder throws it away.
C. Heartbeat only peeks events, it does not consume processed ones
File:
dist/heartbeat-runner-U2x6TbnN.jsdist/system-events-BwxCjOwe.jsHeartbeat uses:
System events are only cleared by:
but heartbeat does not drain processed exec events after successful handling.
That means the same bad exec-completion cue can survive into later heartbeat runs and repeat.
Expected behavior
For exec-event heartbeat handling:
Actual behavior
Impact
Minimal fix direction
pendingEventEntries.Notes
This issue is distinct from the isolated cron session inheritance bug involving stale
lastHeartbeatTextcarry-over.That other bug affects session-store initialization.
This issue is about heartbeat exec-event prompt construction and event consumption.