openclaw - ✅(Solved) Fix Control UI exposes internal-only async exec completion system messages to users [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70458Fetched 2026-04-24 05:57:47
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×2

Control UI currently exposes async exec completion system messages on the user surface, even when the event is explicitly marked for internal handling only.

This creates noisy and confusing UX:

  • the user sees a raw system event like Exec completed (...)
  • the agent is then instructed to handle the result internally
  • the assistant reply looks unnatural because the user already saw the internal event

Error Message

In Control UI / webchat, after an async exec finishes, a system-visible message appears like:

Root Cause

This creates noisy and confusing UX:

  • the user sees a raw system event like Exec completed (...)
  • the agent is then instructed to handle the result internally
  • the assistant reply looks unnatural because the user already saw the internal event

Fix Action

Fixed

PR fix notes

PR #70519: fix(heartbeat): hide internal-only exec transcript turns (Fixes #70458)

Description (problem / solution / changelog)

Summary

Problem

Control UI could surface internal-only async exec completion turns after they had already been handled by heartbeat wake logic.

Why it matters

That leaks internal process noise into visible chat history and makes the UI show content that was explicitly meant to stay private unless the agent chose to surface something user-facing.

What changed

  • heartbeat runs now snapshot the active transcript before the reply turn starts
  • internal-only exec-completion wakes with no delivery target prune any newly appended heartbeat transcript turn before returning
  • added a regression test that simulates an internal-only exec heartbeat appending transcript output and verifies the transcript is restored

What did NOT change

  • no changes to user-relayable exec completions
  • no changes to normal heartbeat delivery behavior for channels with real targets
  • no frontend-only filtering hack; the fix stays at the heartbeat/transcript boundary

Change Type

  • Bug fix

Scope

  • Heartbeat runner
  • Infra regression test

Linked Issue

Closes #70458

User-visible / Behavior Changes

  • Control UI should no longer pick up internal-only exec completion heartbeat turns from chat history
  • relayable exec completions still behave the same way

Security Impact

  • Reduces leakage of internal-only operational content onto the visible chat surface

Repro + Verification

Environment

  • Local worktree off current upstream/main

Steps

  1. Queue an exec completion system event
  2. Run heartbeat with target: "none"
  3. Simulate the heartbeat turn appending assistant output into the transcript

Expected

  • the internal-only exec wake should not leave a visible transcript turn behind

Actual before this change

  • the transcript kept the heartbeat turn, so Control UI history could later surface it

Actual after this change

  • the heartbeat runner truncates the transcript back to its pre-reply size for internal-only exec wakes with no delivery target

Evidence

  • Added regression coverage in src/infra/heartbeat-runner.returns-default-unset.test.ts
  • git diff --check passes locally
  • The repo’s Vitest harness currently fails before file collection in this environment because test/non-isolated-runner.ts extends an undefined TestRunner, so I could not get a clean targeted test run locally from the stock runner
  • Narrow TypeScript re-checking of the changed area showed no diagnostics referencing heartbeat-runner

Human Verification

  • Reviewed the no-target exec heartbeat path to ensure pruning happens only for internal-only exec wakes

Compatibility / Migration

  • None

Failure Recovery

  • If this needs to be reverted, the change is isolated to heartbeat transcript handling and its regression test

Risks and Mitigations

  • Risk: pruning could remove transcript output for the wrong heartbeat path
  • Mitigation: the guard is limited to exec-completion wakes that cannot relay to a user target

Changed files

  • src/infra/heartbeat-runner.returns-default-unset.test.ts (modified, +139/-0)
  • src/infra/heartbeat-runner.ts (modified, +57/-1)

PR #67273: Fix heartbeat async exec delivery leaks

Description (problem / solution / changelog)

PR-ready package: Fix heartbeat async exec delivery leaks

Suggested PR title

Fix heartbeat async exec delivery leaks

Suggested PR summary

This backports the heartbeat async-completion fix that was previously applied only as a live dist patch.

Problem

Even after exec-event prompts were switched to internal-only, async command completion could still leak into user-visible chat because the heartbeat delivery path continued to special-case exec completion.

Root cause

The remaining leak was in src/infra/heartbeat-runner.ts, not just in prompt wording:

  • execFallbackText could revive text already suppressed by heartbeat token stripping
  • shouldSkipMain = normalized.shouldSkip && !normalized.hasMedia && !hasExecCompletion excluded exec completion from the silent main-delivery path
  • canAttemptHeartbeatOk and reasoning delivery were still allowed to flow outward for exec completion events

Fix

  • make exec completion prompt internal-only in src/infra/heartbeat-events-filter.ts
  • remove exec-completion text revival (execFallbackText)
  • force exec completion into the silent main-delivery path
  • prevent outward HEARTBEAT_OK delivery for exec completion
  • prevent outward reasoning payload delivery for exec completion

Tests

Updated / added regression coverage in:

  • src/infra/heartbeat-events-filter.test.ts
  • src/infra/heartbeat-runner.returns-default-unset.test.ts

Regression assertions now cover:

  • internal-only exec prompt regardless of deliverToUser
  • no external HEARTBEAT_OK delivery for exec completion
  • no external reasoning payload delivery for exec completion

Local validation already completed

  • corepack pnpm install --frozen-lockfile
  • git diff --check
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts src/infra/heartbeat-events-filter.test.ts src/infra/heartbeat-runner.returns-default-unset.test.ts
    • exit code: 0

Local source branch

  • fix/heartbeat-async-exec-delivery-backport

Commit

  • f83d4a15ddf2ca6c6d96c56f4baf27d1dc557c59

Transfer artifacts

  • patch diff: heartbeat-async-upstream-backport.patch
  • format-patch: heartbeat-async-upstream-backport-formatpatch.patch
  • result note: heartbeat-async-upstream-backport-result.md

Cherry-pick instruction

git cherry-pick f83d4a15ddf2ca6c6d96c56f4baf27d1dc557c59

Push / PR note

No external push was performed in this step. This package is prepared for manual push / PR creation once explicitly approved.

Changed files

  • src/infra/heartbeat-events-filter.test.ts (modified, +24/-6)
  • src/infra/heartbeat-events-filter.ts (modified, +4/-10)
  • src/infra/heartbeat-runner.returns-default-unset.test.ts (modified, +96/-0)
  • src/infra/heartbeat-runner.ts (modified, +4/-16)

Code Example

System (untrusted): [timestamp] Exec completed (session-id, code 0) :: ...
An async command you ran earlier has completed. The result is shown in the system messages above. Handle the result internally. Do not relay it to the user unless explicitly requested.

---

function buildExecEventPrompt(opts) {
  if (!(opts?.deliverToUser ?? true)) {
    return "An async command you ran earlier has completed. The result is shown in the system messages above. Handle the result internally. Do not relay it to the user unless explicitly requested.";
  }
  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.";
}

---

canRelayToUser: Boolean(delivery.channel !== "none" && delivery.to && visibility.showAlerts)
RAW_BUFFERClick to expand / collapse

Summary

Control UI currently exposes async exec completion system messages on the user surface, even when the event is explicitly marked for internal handling only.

This creates noisy and confusing UX:

  • the user sees a raw system event like Exec completed (...)
  • the agent is then instructed to handle the result internally
  • the assistant reply looks unnatural because the user already saw the internal event

Observed behavior

In Control UI / webchat, after an async exec finishes, a system-visible message appears like:

System (untrusted): [timestamp] Exec completed (session-id, code 0) :: ...
An async command you ran earlier has completed. The result is shown in the system messages above. Handle the result internally. Do not relay it to the user unless explicitly requested.

This message should be internal-only, but it is rendered to the user.


Expected behavior

If the event is marked as internal-only / non-relay:

  • the raw async exec completion system event should not be shown to the user surface
  • it should only wake the agent internally
  • the agent may then decide whether there is anything worth surfacing

In short: internal-only exec completion events should not leak into Control UI chat rendering.


Why this looks like a bug

OpenClaw already has explicit internal-vs-user relay logic for exec completion prompts.

In the current package, the heartbeat runner contains:

function buildExecEventPrompt(opts) {
  if (!(opts?.deliverToUser ?? true)) {
    return "An async command you ran earlier has completed. The result is shown in the system messages above. Handle the result internally. Do not relay it to the user unless explicitly requested.";
  }
  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.";
}

And later:

canRelayToUser: Boolean(delivery.channel !== "none" && delivery.to && visibility.showAlerts)

So the backend/event layer already distinguishes:

  • relay-to-user
  • internal-only

The problem appears to be that Control UI still renders the system event even when deliverToUser = false.


Suspected root cause

Likely one of:

  1. Control UI is rendering async exec completion system events without respecting internal-only visibility
  2. session event injection does not distinguish internal-only system events from user-visible ones
  3. the event is correctly marked for internal handling, but the frontend/system-message renderer still shows it

Affected version

Observed on:

  • local install: OpenClaw 2026.4.20 (115f05d)

Checked package diff against:

  • 2026.4.21

The relevant exec completion / heartbeat prompt logic appears unchanged in 2026.4.21, so this issue likely still exists there as well.


Repro steps

  1. In Control UI / webchat, run a tool flow that starts an async exec
  2. Let the command complete in background
  3. Wait for the completion event to wake the session
  4. Observe that the raw system event is shown in the chat UI, even when the instruction says to handle it internally

Impact

This leaks internal process noise into the user conversation and makes the assistant feel awkward / self-referential.

It is especially confusing because the user sees:

  • the raw internal system event
  • followed by an assistant reply that behaves as if the event was private

Suggested fix

Hide or suppress async exec completion system messages from Control UI when they are internal-only (deliverToUser = false).

Only user-relayable completion events should appear on the visible chat surface.

extent analysis

TL;DR

The issue can be fixed by modifying the Control UI to respect the deliverToUser flag and not render async exec completion system events when set to false.

Guidance

  • Review the Control UI rendering logic to ensure it checks the deliverToUser flag before displaying system events.
  • Verify that the buildExecEventPrompt function is correctly handling the deliverToUser option and returning the expected prompt.
  • Check the session event injection logic to ensure it distinguishes between internal-only and user-visible system events.
  • Test the fix by running the repro steps and verifying that the raw system event is no longer shown in the chat UI when deliverToUser is false.

Example

if (opts.deliverToUser) {
  // render the system event
} else {
  // do not render the system event
}

Notes

The fix may require changes to the Control UI rendering logic and/or the session event injection logic. The buildExecEventPrompt function appears to be correctly handling the deliverToUser option, but the issue may be elsewhere in the code.

Recommendation

Apply a workaround to hide or suppress async exec completion system messages from Control UI when they are internal-only (deliverToUser = false). This can be done by modifying the rendering logic to respect the deliverToUser flag.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

If the event is marked as internal-only / non-relay:

  • the raw async exec completion system event should not be shown to the user surface
  • it should only wake the agent internally
  • the agent may then decide whether there is anything worth surfacing

In short: internal-only exec completion events should not leak into Control UI chat rendering.


Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Control UI exposes internal-only async exec completion system messages to users [2 pull requests, 1 participants]