Summary
The heartbeat exec_completion handler can spawn a user-facing agent turn with no command output attached, producing phantom replies like "I don't see any system messages above with command output to relay..." delivered to end users on Telegram/Discord/etc.
Environment
- OpenClaw
2026.4.21
- Channel: Telegram (direct chat)
- Trigger: any
exec call with background: true whose process later exits
Steps to reproduce
- Agent runs an
exec with background: true that completes quickly (e.g. brew install <pkg>, openclaw doctor, a shell script).
- The originating agent turn ends normally.
- When the detached process exits, the runtime posts a system event (
exec completed (session-id, code 0)) to the timeline.
heartbeat-runner detects via isExecCompletionEvent() and spawns an isolated agent turn using buildExecEventPrompt({ deliverToUser: canRelayToUser }).
- In the isolated session, the actual stdout is not attached — the "system messages above" referenced in the prompt do not exist.
- The agent correctly notes "I don't see any system messages above..." and, because
canRelayToUser=true (Telegram channel configured with visibility.showAlerts=true), the reply is delivered to the end user.
Reference in built code (paths relative to @openclaw/openclaw@2026.4.21):
// dist/heartbeat-runner-DV6vG6Se.js
function isExecCompletionEvent(evt) {
return /^exec finished(?::|\s*\()/.test(evt) || /^exec (completed|failed) \(.../.test(evt);
}
function buildExecEventPrompt(opts) {
if (!(opts?.deliverToUser ?? true)) return "...Handle the result internally. Do not relay it to the user...";
return "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.";
}
// later
const shouldSkipMain = normalized.shouldSkip && !normalized.hasMedia && !hasExecCompletion;
// ^^^^^^^^^^^^^^^^^^^^^^^
// exec completion forces a reply even when the agent returns empty.
Expected
If the completion event has no attached stdout/stderr, the runtime should either:
- Attach the actual command output before spawning the relay turn, or
- Skip the user-facing turn entirely (no delivery, no phantom reply).
Actual
A fresh isolated agent session is spawned with only the relay prompt (no output), the agent apologises for missing output, and hasExecCompletion forces the reply to be delivered to the end user. The user sees an out-of-context apology that has no relation to anything they asked.
Suggested fix
- Before spawning the relay turn, verify the exec result payload is non-empty. If empty, emit the completion to internal logs only and skip the agent turn.
- Alternatively, add a config knob:
agents.defaults.heartbeat.suppressExecCompletionRelay: true (or channels.*.heartbeat.suppressExecCompletionRelay) that short-circuits the relay path without affecting genuine heartbeat alerts (unlike the existing showAlerts: false, which is too coarse).
- Consider removing the
!hasExecCompletion forced-override in the shouldSkipMain calculation so that agent-side NO_REPLY works as the fallback.
Workaround users can apply today
- Agent-side: avoid
exec with background: true for short-running commands. Use synchronous exec with adequate yieldMs so the process completes in-turn.
- Runtime-side:
channels.<name>.heartbeat.showAlerts: false will switch the prompt variant to "handle internally" — but it also suppresses legitimate heartbeat alerts, so it is too blunt for most users.
Evidence
In my deployment, three orphan sessions were generated today with only the relay prompt and no attached output — timestamps correlate exactly with backgrounded exec calls (brew install, gateway-restart, doctor checks). Each produced the phantom apology and delivered it to the user's Telegram chat.
Summary
The heartbeat
exec_completionhandler can spawn a user-facing agent turn with no command output attached, producing phantom replies like "I don't see any system messages above with command output to relay..." delivered to end users on Telegram/Discord/etc.Environment
2026.4.21execcall withbackground: truewhose process later exitsSteps to reproduce
execwithbackground: truethat completes quickly (e.g.brew install <pkg>,openclaw doctor, a shell script).exec completed (session-id, code 0)) to the timeline.heartbeat-runnerdetects viaisExecCompletionEvent()and spawns an isolated agent turn usingbuildExecEventPrompt({ deliverToUser: canRelayToUser }).canRelayToUser=true(Telegram channel configured withvisibility.showAlerts=true), the reply is delivered to the end user.Reference in built code (paths relative to
@openclaw/openclaw@2026.4.21):Expected
If the completion event has no attached stdout/stderr, the runtime should either:
Actual
A fresh isolated agent session is spawned with only the relay prompt (no output), the agent apologises for missing output, and
hasExecCompletionforces the reply to be delivered to the end user. The user sees an out-of-context apology that has no relation to anything they asked.Suggested fix
agents.defaults.heartbeat.suppressExecCompletionRelay: true(orchannels.*.heartbeat.suppressExecCompletionRelay) that short-circuits the relay path without affecting genuine heartbeat alerts (unlike the existingshowAlerts: false, which is too coarse).!hasExecCompletionforced-override in theshouldSkipMaincalculation so that agent-sideNO_REPLYworks as the fallback.Workaround users can apply today
execwithbackground: truefor short-running commands. Use synchronous exec with adequateyieldMsso the process completes in-turn.channels.<name>.heartbeat.showAlerts: falsewill switch the prompt variant to "handle internally" — but it also suppresses legitimate heartbeat alerts, so it is too blunt for most users.Evidence
In my deployment, three orphan sessions were generated today with only the relay prompt and no attached output — timestamps correlate exactly with backgrounded
execcalls (brew install, gateway-restart, doctor checks). Each produced the phantom apology and delivered it to the user's Telegram chat.