Summary
When an async exec command completes and triggers a heartbeat, the agent consistently replies with a variant of "I don't see any async command output in the system messages above". This is a bug in buildExecEventPrompt() — the actual exec output is never injected into the prompt.
Code trace
File: src/infra/heartbeat-runner.ts (compiled: dist/heartbeat-runner-CInrztfM.js)
Line 167-169 — buildExecEventPrompt() returns a static string:
"An async command you ran earlier has completed. The result is shown in the system messages above."
Line 512 — hasExecCompletion is detected from event text (correct):
const hasExecCompletion = pendingEvents.some(isExecCompletionEvent);
Line 539 — buildExecEventPrompt() is called with no event data:
prompt: hasExecCompletion ? buildExecEventPrompt({ deliverToUser: params.canRelayToUser }) : ...
The exec event entries are mapped to text at line 510 (pendingEvents) and checked for exec completion at line 512, but the actual event text (which contains the compactOutput) is never passed to buildExecEventPrompt(). The model is told to look at system messages that were never injected.
Reproduction
- Run a background exec command via the
exec tool with background: true
- Wait for heartbeat to fire after the exec completes
- Observe agent replies:
"I don't see any async command output in the system messages above. Could you share the result?"
Expected behaviour
The exec output should be included inline in the heartbeat prompt, so the agent can actually see and relay it.
Proposed fix
Pass the exec event entries through to buildExecEventPrompt() and include compactOutput inline:
function buildExecEventPrompt(opts: { deliverToUser?: boolean; execEvents?: string[] }) {
const outputSection = opts.execEvents?.length
? `\n\nExec output:\n${opts.execEvents.join("\n")}`
: "";
if (!(opts?.deliverToUser ?? true)) {
return `An async command you ran earlier has completed.${outputSection}\nHandle the result internally. Do not relay it to the user unless explicitly requested.`;
}
return `An async command you ran earlier has completed.${outputSection}\nPlease 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.`;
}
Then at line 539:
hasExecCompletion ? buildExecEventPrompt({
deliverToUser: params.canRelayToUser,
execEvents: pendingEvents.filter(isExecCompletionEvent)
}) : ...
Environment
- OpenClaw: 2026.4.15 (041266a)
- macOS Darwin 25.3.0 (arm64)
- Node 24.14.0
Impact
Every exec-completion heartbeat produces a confusing "I don't see the output" reply. Users cannot rely on background exec completion notification via heartbeat.
Summary
When an async exec command completes and triggers a heartbeat, the agent consistently replies with a variant of
"I don't see any async command output in the system messages above". This is a bug inbuildExecEventPrompt()— the actual exec output is never injected into the prompt.Code trace
File:
src/infra/heartbeat-runner.ts(compiled:dist/heartbeat-runner-CInrztfM.js)Line 167-169 —
buildExecEventPrompt()returns a static string:Line 512 —
hasExecCompletionis detected from event text (correct):Line 539 —
buildExecEventPrompt()is called with no event data:The exec event entries are mapped to text at line 510 (
pendingEvents) and checked for exec completion at line 512, but the actual event text (which contains thecompactOutput) is never passed tobuildExecEventPrompt(). The model is told to look at system messages that were never injected.Reproduction
exectool withbackground: true"I don't see any async command output in the system messages above. Could you share the result?"Expected behaviour
The exec output should be included inline in the heartbeat prompt, so the agent can actually see and relay it.
Proposed fix
Pass the exec event entries through to
buildExecEventPrompt()and includecompactOutputinline:Then at line 539:
Environment
Impact
Every exec-completion heartbeat produces a confusing "I don't see the output" reply. Users cannot rely on background exec completion notification via heartbeat.