fix(agents): deliver background exec completion to agent via [OpenClaw exec completion]#91921
fix(agents): deliver background exec completion to agent via [OpenClaw exec completion]#91921solodmd wants to merge 1 commit into
Conversation
|
Codex review: needs changes before merge. Reviewed June 30, 2026, 5:04 PM ET / 21:04 UTC. Summary PR surface: Source +37, Tests +129. Total +166 across 8 files. Reproducibility: yes. source-level. Current main uses the generic heartbeat transcript marker for exec-event heartbeats and tells delivery-disabled exec prompts to return Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Land a scoped exec-completion transcript path that is visible to hooks and the model while keeping heartbeat-only repair predicates distinct, preserving delivery suppression, and coordinating with broader exec wake work instead of duplicating it. Do we have a high-confidence way to reproduce the issue? Yes, source-level. Current main uses the generic heartbeat transcript marker for exec-event heartbeats and tells delivery-disabled exec prompts to return Is this the best way to solve the issue? No, not as submitted. The body-visible exec-completion marker is the right direction, but routing it through the generic heartbeat classifier is too broad until doctor/session repair behavior is scoped or covered. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against b885c81479d0. Label changesLabel justifications:
Evidence reviewedPR surface: Source +37, Tests +129. Total +166 across 8 files. View PR surface stats
Acceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
1d5ce42 to
6e99312
Compare
6e99312 to
2616486
Compare
2616486 to
2a4ea34
Compare
2a4ea34 to
c22d58d
Compare
c22d58d to
b580bd7
Compare
|
@clawsweeper review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
9a71984 to
6f7d2c4
Compare
|
@clawsweeper review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
This pull request has been automatically marked as stale due to inactivity. |
Summary
exec background=true) completes, the agent receives a heartbeat wake with[OpenClaw heartbeat poll]as the transcript placeholder. This causes the model to treat the turn as a heartbeat poll — it readsHEARTBEAT.mdand either runs periodic tasks or repliesHEARTBEAT_OK, ignoring the exec completion details.[OpenClaw exec completion]transcript placeholder with the exec details appended in the message body, making them visible to both the model andbefore_agent_runsecurity plugins.deliverToUseris false,buildExecEventPromptnow shows exec output with "Handle the result internally. Continue the task based on the result if needed" instead of "HEARTBEAT_OK only" — so the model sees completion details in any active conversation without overriding delivery authorization.HEARTBEAT.mdand replyingHEARTBEAT_OK.[OpenClaw exec completion]\n\n...), not in a separatecustom_message, sobefore_agent_runhooks and security plugins can inspect the full content.isExecCompletionUserMessagefilter usesstartsWith("EXEC_COMPLETION_TRANSCRIPT_PROMPT\n")to handle the new body format.deliverToUserisparams.canRelayToUseronly — no override. Instead,buildExecEventPrompt's!deliverToUserbranch now shows exec output with "handle internally, continue task" instructions.lastChannelparameter has been removed fromresolveHeartbeatRunPrompt.Linked context
No related issue directly — discovered while investigating exec background completion notification behavior.
Related:
HEARTBEAT_TRANSCRIPT_PROMPT("[OpenClaw heartbeat poll]") pattern established the transcript placeholder filtering mechanism that this PR extends for exec completions.ClawSweeper feedback addressed
P1/P2: Preserve internal-only exec delivery
The original P1 finding flagged hard-coding
deliverToUser: trueas bypassing thetarget: "none"delivery contract. Subsequent reviews flagged that rawlastChannelshould not override delivery authorization. This PR addresses both concerns by separating exec event visibility from delivery authorization withinbuildExecEventPrompt:deliverToUserstays tied to canonical delivery:deliverToUser: params.canRelayToUser— no override. ThelastChannelparameter has been removed fromresolveHeartbeatRunPrompt. For webchat (wherecanRelayToUseris false because there's notoaddress),deliverToUserisfalse.Exec output is visible regardless of
deliverToUser:When
deliverToUserisfalse,buildExecEventPromptno longer says "reply HEARTBEAT_OK only" or "do not mention, summarize, or reuse command output." Instead, it includes the completion details with output and instructs the model to "Handle the result internally. Continue the task based on the result if needed." This way:deliverToUseroverride is neededtarget: "none"is naturally respected (deliverToUser stays false, no special check needed)target: "none"also get the same improved promptWhy this approach:
canRelayToUserchecks whether the delivery system can actively push a message through the channel's addressing system (requires bothchannelandto). Background exec completion in an active webchat session is a different scenario — the message doesn't need external delivery; the model is already in the conversation and just needs to see the result. Rather than overridingdeliverToUserto bypass this distinction, the prompt itself is adapted to show context without instructing relay.Security gate visibility:
Unlike PR #91817 (which was closed because
before_agent_runsecurity plugins couldn't see exec details hidden incustom_message), this PR puts exec details directly in the message body viatranscriptBody = "${EXEC_COMPLETION_TRANSCRIPT_PROMPT}\n\n${effectiveBaseBody}". Security plugins inspecting user messages will see[OpenClaw exec completion]\n\nExec completed (...)— the full content the model receives, not just a placeholder.Why exec details persist in transcriptBody:
Background exec is transparent to the user — the command runs asynchronously and completes without the user's direct involvement. When it finishes, the result needs to be visible in the ongoing conversation so the user can see what happened and the model can reference it in follow-up turns. An ephemeral-only design (visible on the current turn then discarded) would mean:
The
[OpenClaw exec completion]marker makes these entries distinguishable from regular user messages, and the existingfilterHeartbeatTranscriptArtifactsmechanism already handles cleanup — it only retains the entry when the model's response is substantive (i.e., the model actually used the exec result). This is intentional: if the model responded to the exec output, that context is relevant for future turns. The same design principle applies to regular command output — it becomes part of the conversation history.Real behavior proof
Behavior or issue addressed: Background exec completion notification reaches the model as a heartbeat poll, causing the model to ignore exec output and run heartbeat tasks instead.
Real environment tested: Local gateway with a Qwen3 family model, behind an OpenAI-compatible proxy that logs all LLM requests/responses.
Exact steps or command run after this patch: Ran
exec background=true command="sleep 10 && echo 'first done'", waited ~10 seconds for the exec completion wake, then inspected the proxy logs.Evidence after fix:
User message body sent to the model (exec details inline, visible to security plugins):
Model response:
Before evidence:
Last user message (shows
[OpenClaw heartbeat poll]instead of exec details):Model response:
HEARTBEAT_OK(model treated it as a heartbeat poll, ignored exec completion)Observed result after fix: The
[OpenClaw exec completion]placeholder prevents the heartbeat behavior. The model sees the exec output in the runtime context and responds appropriately with the command results.What was not tested: Multi-line exec output, failed commands (non-zero exit), long-running commands with partial output.
Proof limitations: Evidence is from an intercepted LLM request log, not from OpenClaw gateway logs directly. The streaming output was reconstructed from SSE chunks.
Patch surface
src/infra/heartbeat-events-filter.ts!deliverToUserbranch shows exec output + "handle internally, continue task" instead of HEARTBEAT_OK; addedhasMissingOutputFailuresub-branchsrc/infra/heartbeat-runner.tslastChannelparam;deliverToUser: params.canRelayToUseronlysrc/auto-reply/heartbeat.tsEXEC_COMPLETION_TRANSCRIPT_PROMPTconstantsrc/auto-reply/reply/prompt-prelude.tstranscriptBodyincludes exec details fromeffectiveBaseBodysrc/auto-reply/heartbeat-filter.tsisExecCompletionUserMessageusesstartsWithfor body formatsrc/auto-reply/heartbeat-filter.test.tssrc/auto-reply/reply/prompt-prelude.test.tssrc/infra/heartbeat-events-filter.test.tsdeliverToUser: false+hasMissingOutputFailuretestTests and validation
pnpm test src/auto-reply/heartbeat-filter.test.ts: All tests pass.pnpm test src/auto-reply/reply/prompt-prelude.test.ts: All tests pass.pnpm test src/infra/heartbeat-runner.ghost-reminder.test.ts: All tests pass.isExecCompletionUserMessagewith exec details appended in body.isHeartbeatUserMessagewithEXEC_COMPLETION_TRANSCRIPT_PROMPTreturnstrue.filterHeartbeatTranscriptArtifactsremoving exec completion pairs alongside heartbeat pairs.buildReplyPromptEnvelopewith exec-event Provider:transcriptCommandBodyincludes exec details.Risk checklist
Yes— exec completion notifications now properly reach the model's attention instead of being treated as heartbeat polls.NoNostartsWithinstead of exact match, which is correct since exec details are appended after the marker.EXEC_COMPLETION_TRANSCRIPT_PROMPTprefix with a newline separator before any exec details. Unit tests cover both the bare marker and marker-with-details formats.Current review state