openclaw - ✅(Solved) Fix [Bug]: exec completion events leak into webchat conversation as System messages [2 pull requests, 3 comments, 4 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#65994Fetched 2026-04-14 05:39:21
View on GitHub
Comments
3
Participants
4
Timeline
6
Reactions
0
Author
Timeline (top)
commented ×3labeled ×2cross-referenced ×1

Async exec completion events are currently routed through the heartbeat/alert path and end up being rendered into the webchat/control-ui conversation as visible System messages.

This pollutes the user-facing transcript with internal runtime notifications that should not appear as normal chat messages.

Root Cause

Async exec completion events are currently routed through the heartbeat/alert path and end up being rendered into the webchat/control-ui conversation as visible System messages.

This pollutes the user-facing transcript with internal runtime notifications that should not appear as normal chat messages.

Fix Action

Workaround

A temporary workaround is:

"channels": {
  "defaults": {
    "heartbeat": {
      "showOk": false,
      "showAlerts": false,
      "useIndicator": false
    }
  }
}

This suppresses the noise, but does not address the underlying issue that internal exec completion events are eligible to enter the chat transcript at all.

PR fix notes

PR #66156: [codex] fix exec completion system-event history leak

Description (problem / solution / changelog)

Summary

  • Problem: background exec completion events could leak into webchat and session-history user messages as leading System (untrusted) text.
  • Why it matters: Control UI history could show internal system-event noise as if the user sent it, and modern session transcripts could keep the polluted user turn.
  • What changed: added a display-only system-event prefix stripper for user-visible history/render paths, aligned /sessions/:key/history snapshot/SSE sanitization with chat.history, and rewrote newly written modern session transcripts back to raw user text when this leak occurred.
  • What did NOT change (scope boundary): this PR does not change exec/system-event generation, trust semantics, or any third-party integration behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #65994
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: queued exec completion system events were reaching user-visible history/render paths as ordinary user text, and the modern webchat transcript path could persist that polluted user turn verbatim.
  • Missing detection / guardrail: there was no regression coverage ensuring chat.history, /sessions/:key/history, and UI render helpers all stripped the same injected system-event prefix shape.
  • Contributing context (if known): stripInboundMetadata could not safely own this globally because non-display call sites like command detection must preserve literal user text, so the fix needed a display-only seam.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/auto-reply/reply/strip-inbound-meta.test.ts
    • src/gateway/chat-sanitize.test.ts
    • src/gateway/session-history-state.test.ts
    • src/gateway/server-methods/server-methods.test.ts
    • ui/src/ui/chat/message-extract.test.ts
    • ui/src/ui/chat/message-normalizer.test.ts
  • Scenario the test should lock in: when a user turn begins with injected System (untrusted) exec completion lines followed by visible user text, history/rendered text should show only the visible user text; modern session transcripts should be rewritten back to the raw user message; legacy raw JSONL transcripts should be left untouched.
  • Why this is the smallest reliable guardrail: the bug crosses a shared helper, gateway history, session-history SSE/snapshot, transcript rewrite, and UI leaf renderers, so the regression needs coverage at those seams rather than only a single browser path.
  • Existing test that already covers this (if any): None.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • Webchat and session-history surfaces no longer display queued exec completion events as leading System (untrusted) user text.
  • Newly written modern session transcripts are cleaned back to the raw user message text when this leak path is hit.

Diagram (if applicable)

Before:
[user message + queued exec completion event] -> [transcript/history stores prefixed user turn] -> [chat history renders leaked System text]

After:
[user message + queued exec completion event] -> [display/history sanitizers strip prefix] -> [modern transcript rewrite restores raw user turn] -> [history renders only user-authored text]

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local checkout, Node v25.8.1
  • Model/provider: N/A
  • Integration/channel (if any): webchat / Control UI, gateway session-history HTTP/SSE
  • Relevant config (redacted): default gateway history settings

Steps

  1. Send a webchat user message in a session where an exec completion event is queued into the conversation.
  2. Reload chat.history or /sessions/:key/history, or render the turn in the Control UI.
  3. Inspect the visible user turn and the stored modern session transcript entry.

Expected

  • Only the user-authored message text is visible, and modern session transcripts keep the raw user message text.

Actual

  • Before this fix, the user turn could begin with System (untrusted): [..] Exec completed ... lines.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: targeted tests for helper stripping, gateway history sanitization, session-history snapshot/SSE sanitization, transcript rewrite, and UI extraction/normalization; pnpm build locally on the rebased branch.
  • Edge cases checked: literal user-authored timestamped System: lines are preserved; display-only stripping does not affect command detection paths; legacy raw JSONL transcripts are not rewritten.
  • What you did not verify: manual browser clickthrough on the rebased branch; pnpm tsgo is currently red on latest upstream/main from unrelated pre-existing failures outside this diff.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: system-event stripping matches the current injected prefix shape, so a future prompt-shape change could reintroduce visible leakage.
    • Mitigation: direct regression tests cover the helper, gateway history, session-history snapshot/SSE, transcript rewrite, and UI render helpers.
  • Risk: transcript rewrite touches stored history for modern session-manager transcripts.
    • Mitigation: the rewrite is gated to session-header transcripts only, is a no-op when content/media already match, and explicitly skips legacy raw JSONL transcripts.

Changed files

  • src/auto-reply/reply/strip-inbound-meta.test.ts (modified, +28/-1)
  • src/auto-reply/reply/strip-inbound-meta.ts (modified, +50/-0)
  • src/gateway/chat-sanitize.test.ts (modified, +12/-0)
  • src/gateway/chat-sanitize.ts (modified, +10/-3)
  • src/gateway/server-methods/chat.ts (modified, +67/-13)
  • src/gateway/server-methods/server-methods.test.ts (modified, +73/-0)
  • src/gateway/session-history-state.test.ts (modified, +40/-0)
  • src/gateway/session-history-state.ts (modified, +7/-2)
  • ui/src/ui/chat/message-extract.test.ts (modified, +17/-0)
  • ui/src/ui/chat/message-extract.ts (modified, +5/-2)
  • ui/src/ui/chat/message-normalizer.test.ts (modified, +12/-0)
  • ui/src/ui/chat/message-normalizer.ts (modified, +8/-2)

PR #67036: fix(ui): filter leaked control ui transcript rows

Description (problem / solution / changelog)

Summary

  • Problem: Control UI can render leaked internal transcript rows such as System: / System (untrusted): exec-status lines and sender-envelope noise as normal chat messages.
  • Why it matters: these rows expose internal plumbing in the user-visible transcript and make webchat feel broken and untrustworthy.
  • What changed: added history-side filtering in controllers/chat.ts, added a render-time guard in views/chat.ts, and added regression tests for both layers.
  • What did NOT change (scope boundary): this PR does not change backend event production or session history serialization. It only hardens the UI against known leaked row shapes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #66814
  • Related #65994
  • Related #62418
  • Related #66648
  • Related #39473
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: Control UI trusted some transcript/history rows that were clearly internal-status content and rendered them as ordinary chat messages.
  • Missing detection / guardrail: there was no single defensive filter covering both history ingestion and final render-time grouping.
  • Contributing context (if known): some leaked rows can arrive with inconsistent roles or replay from local chat state, so filtering in only one layer was not enough.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • ui/src/ui/controllers/chat.test.ts
    • ui/src/ui/views/chat.test.ts
  • Scenario the test should lock in:
    • leaked System (untrusted): ... Exec completed ... rows are dropped from loaded history
    • leaked sender-metadata + exec-status rows are dropped from loaded history
    • leaked rows are not rendered even if they reach the chat view
  • Why this is the smallest reliable guardrail:
    • the regression is in UI-side message filtering and rendering, and these tests exercise exactly those paths without requiring a full gateway repro
  • Existing test that already covers this (if any):
    • none specific to leaked internal transcript rows
  • If no new test is added, why not:
    • N/A

User-visible / Behavior Changes

  • Control UI no longer shows known leaked internal exec-status transcript rows as visible conversation bubbles.
  • Control UI no longer renders known sender-envelope leak shapes tied to exec status text.

Diagram (if applicable)

Before:
[leaked internal row reaches history/view] -> [rendered as normal chat bubble]

After:
[leaked internal row reaches history/view] -> [filtered at load and render guard] -> [not shown]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local OpenClaw install, web Control UI
  • Model/provider: N/A
  • Integration/channel (if any): webchat / Control UI
  • Relevant config (redacted): default local Control UI flow

Steps

  1. Open Control UI on a session where leaked internal exec/system transcript rows are visible.
  2. Load or refresh chat history.
  3. Observe whether System: / System (untrusted): exec-status rows or sender-envelope noise appear as normal chat bubbles.

Expected

  • internal exec/system leak rows are not shown in the visible chat transcript

Actual

  • before this change, leaked internal rows could appear as normal visible chat content

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios:
    • locally patched Control UI no longer showed leaked System (untrusted) / exec rows after refresh
    • sender-envelope leak shapes were covered in filtering logic
  • Edge cases checked:
    • leaked rows with inconsistent role assignment
    • leaked rows replayed from local recent chat state
  • What you did not verify:
    • full upstream CI/test suite in the source repo
    • every possible backend leak shape outside the known signatures covered here

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk:
    • the filter could hide a legitimate user-visible message if it exactly matches a known leak signature
    • Mitigation:
      • signatures are intentionally narrow and tied to known internal leak patterns
      • render-time and load-time tests lock in the current intended behavior

Changed files

  • ui/src/ui/chat/export.test.ts (added, +48/-0)
  • ui/src/ui/chat/export.ts (modified, +4/-0)
  • ui/src/ui/chat/history-filter.test.ts (added, +41/-0)
  • ui/src/ui/chat/history-filter.ts (added, +60/-0)
  • ui/src/ui/controllers/chat.test.ts (modified, +126/-0)
  • ui/src/ui/controllers/chat.ts (modified, +2/-16)
  • ui/src/ui/views/chat.test.ts (modified, +57/-0)
  • ui/src/ui/views/chat.ts (modified, +4/-0)

Code Example

"channels": {
  "defaults": {
    "heartbeat": {
      "showOk": false,
      "showAlerts": false,
      "useIndicator": false
    }
  }
}
RAW_BUFFERClick to expand / collapse

Summary

Async exec completion events are currently routed through the heartbeat/alert path and end up being rendered into the webchat/control-ui conversation as visible System messages.

This pollutes the user-facing transcript with internal runtime notifications that should not appear as normal chat messages.

Steps to reproduce

  1. Open OpenClaw Control UI / webchat
  2. In a normal conversation, trigger one or more async/background exec calls
  3. Wait for those commands to finish
  4. Observe that completion notifications are injected back into the conversation as visible System (untrusted): Exec completed ... messages

This is especially obvious when multiple background execs are started in a short period, since each completion can generate another visible System message.

Actual behavior

  • Async exec completions are surfaced as visible System messages inside the webchat conversation
  • These messages are written into the transcript
  • They interrupt the normal user/assistant dialogue flow
  • In bursty cases, they can spam the conversation with multiple internal completion notices

Expected behavior

  • Exec completion events should not be injected into the user-visible webchat transcript by default
  • For webchat/control-ui, they should go to a non-transcript surface instead, such as:
    • an activity indicator
    • a status/event panel
    • a toast/notification layer
  • If a visible chat message is ever needed, it should be explicit opt-in behavior rather than the default

Why this is a bug

This is not just noisy UX. It also mixes internal runtime events with user conversation history, which makes the transcript harder to read and reason about. In control-ui/webchat, internal execution lifecycle events should be separated from the chat stream.

Suspected cause

Likely cause: async exec completion events are treated as heartbeat/alert events, and the webchat delivery path renders those events back into the active conversation as System messages.

Logs, screenshots, and evidence

<img width="1712" height="2510" alt="Image" src="https://github.com/user-attachments/assets/b3c86ea8-1362-4756-9dfb-aed5f002a521" />

Workaround

A temporary workaround is:

"channels": {
  "defaults": {
    "heartbeat": {
      "showOk": false,
      "showAlerts": false,
      "useIndicator": false
    }
  }
}

This suppresses the noise, but does not address the underlying issue that internal exec completion events are eligible to enter the chat transcript at all.

Scope

Confirmed in webchat / control-ui. Not claiming the same behavior is necessarily wrong on every channel.

extent analysis

TL;DR

Separate async exec completion events from the webchat transcript by routing them to a non-transcript surface, such as an activity indicator or status panel.

Guidance

  • Review the current event routing configuration to identify where async exec completion events are being treated as heartbeat/alert events.
  • Investigate alternative routing options for async exec completion events, such as using a separate event channel or handler.
  • Consider implementing a filter or interceptor to prevent internal runtime events from being rendered as System messages in the webchat conversation.
  • Evaluate the temporary workaround provided, which suppresses heartbeat/alert events, but note that it does not address the underlying issue.

Example

No code snippet is provided as the issue does not imply a specific code change, but rather a configuration or architectural adjustment.

Notes

The provided workaround only suppresses the symptoms and does not fix the root cause. A more permanent solution would involve modifying the event routing or handling logic to separate internal runtime events from the user conversation history.

Recommendation

Apply a workaround, such as the provided configuration change, until a more permanent solution can be implemented to properly separate async exec completion events from the webchat transcript. This is because the workaround, although not ideal, can mitigate the issue temporarily while a more thorough fix is developed.

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

  • Exec completion events should not be injected into the user-visible webchat transcript by default
  • For webchat/control-ui, they should go to a non-transcript surface instead, such as:
    • an activity indicator
    • a status/event panel
    • a toast/notification layer
  • If a visible chat message is ever needed, it should be explicit opt-in behavior rather than the default

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 [Bug]: exec completion events leak into webchat conversation as System messages [2 pull requests, 3 comments, 4 participants]