Skip to content

Bug: heartbeat exec-event relay prompt drops the actual system-event body and emits generic fallback text #66382

@nanli2000cn

Description

@nanli2000cn

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:

news_fetcher ok

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:

  1. The heartbeat prompt should include the actual exec completion body, not only a generic cue.
  2. After heartbeat successfully handles the exec completion event, the processed event should be consumed so it does not repeat.

Actual behavior

  1. Heartbeat sees only a generic “async command completed” prompt.
  2. Model lacks the real result body.
  3. Model emits generic fallback English.
  4. Heartbeat may send that fallback to the user.
  5. 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

  1. In heartbeat exec-event prompt construction, include the actual exec completion event text from pendingEventEntries.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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