Skip to content

[Bug]: Control UI / webchat duplicates user message and leaks Sender (untrusted metadata) as openclaw-control-ui #72861

Description

@cslash-zz

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

Control UI / webchat can duplicate a single user message into two visible user turns:

  • one normal local/plain user message
  • one echoed user message whose content begins with Sender (untrusted metadata): and whose sender label resolves to openclaw-control-ui

This appears to be a regression after 2026.4.24 / present in 2026.4.24+ builds.

This is not just double ingress from the browser. The current code path can emit two different user-message representations for the same logical turn.

Steps to reproduce

  1. Update OpenClaw to a recent 2026.4.24+ build.
  2. Open Control UI webchat.
  3. Send a simple message such as 1.
  4. Observe that the same user turn may appear twice.
  5. One duplicate contains/derives from inbound metadata and may show sender label openclaw-control-ui.

Expected behavior

A single webchat user input should produce exactly one visible user turn in Control UI.

Inbound metadata such as Sender (untrusted metadata): must never be shown in user-visible chat history.

Actual behavior

The same logical webchat user turn can be surfaced twice:

  1. a plain user message
  2. a second user message carrying inbound metadata / sender label openclaw-control-ui

OpenClaw version

Observed after update to 2026.4.24; still reproducible in current npm build (2026.4.24+ family)

Operating system

Linux (gateway host), reproduced from browser-based Control UI

Install method

npm / global install

Model

Not model-specific

Provider / routing chain

Browser Control UI (webchat) -> OpenClaw gateway -> main agent session

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Observed duplicate content pattern:

Sender (untrusted metadata):
```json
{
  "label": "openclaw-control-ui",
  "id": "openclaw-control-ui"
}

[Mon 2026-04-27 17:31 GMT+3] 1


### Root cause analysis

The current runtime appears to have **two separate user-turn paths** for the same `webchat` input:

#### 1) Early webchat transcript update path

`/root/.nvm/versions/node/v22.22.2/lib/node_modules/openclaw/dist/chat-DznnoAn8.js`

- `emitUserTranscriptUpdate()` at `2369-2394`
- calls `emitSessionTranscriptUpdate({ message: buildChatSendTranscriptMessage(...) })`
- `buildChatSendTranscriptMessage(...)` at `1263-1270` produces a plain:
  - `role: "user"`
  - `content: params.message`

#### 2) Main reply/transcript path with inbound metadata

`/root/.nvm/versions/node/v22.22.2/lib/node_modules/openclaw/dist/get-reply-CSQ5BMwG.js`

- `buildInboundUserContextPrefix(...)` at `1820`
- inbound user context is assembled into prompt/transcript body around `2231-2242`
- `transcriptCommandBody` is passed onward at `2455-2456` and `2533-2534`

Then runner path forwards transcript prompt:

`/root/.nvm/versions/node/v22.22.2/lib/node_modules/openclaw/dist/agent-runner.runtime-CbAg9IpO.js`

- CLI path: `732`
- embedded path: `831`
- run dispatch: `3070`

#### 3) Transcript update broadcast does not sanitize the visible message

`/root/.nvm/versions/node/v22.22.2/lib/node_modules/openclaw/dist/server.impl-CtLS1ywt.js`

- `createTranscriptUpdateBroadcastHandler(...)` at `9229-9253`
- broadcasts `update.message` via `session.message`
- does **not** call `stripInboundMetadata(...)` / `stripEnvelopeFromMessage(...)`

#### 4) There is already an explicit contract that this metadata must never be displayed

`/root/.nvm/versions/node/v22.22.2/lib/node_modules/openclaw/dist/strip-inbound-meta-DaDVfj8b.js`

Header comment says OpenClaw-injected inbound metadata blocks are AI-facing only and:

> must never surface in user-visible chat history

The sentinel list includes:

- `Conversation info (untrusted metadata):`
- `Sender (untrusted metadata):`

### Interpretation

This strongly suggests a regression where `webchat` now exposes both:

1. the early plain user transcript update
2. a second transcript/broadcast path carrying inbound metadata

So this is not just an external duplicate submit. It is an internal duplication / consumer-sanitization failure.

### Proposed fix

#### Minimal safe mitigation

Sanitize transcript-broadcast user messages before they are sent to Control UI in:

- `createTranscriptUpdateBroadcastHandler(...)`

using the same stripping path already intended for display surfaces, e.g. `stripInboundMetadata(...)` / `stripEnvelopeFromMessage(...)`.

#### Proper fix

Ensure `webchat` produces only **one canonical user-turn representation** per input.

Either:

1. suppress the early `emitUserTranscriptUpdate()` path for normal `webchat` user turns, or
2. suppress the later duplicate transcript-visible user entry when it represents the same logical turn, or
3. add stable dedupe/idempotency across transcript update broadcast for the same webchat user turn.

### Related issues

- #72671 — earlier report with similar symptom, but closed
- #72227 — optimistic `You` vs `openclaw-control-ui` duplicate, closed
- #69208 — umbrella: duplicate transcript / replay / context assembly across channels
- #71992 — related Control UI duplicate regression on assistant side

### Impact and severity

User-visible duplicate messages in Control UI, plus visible leakage of internal inbound metadata.

Also likely increases confusion during transcript/history/debugging and may indicate broader duplicate persistence / duplicate broadcast behavior.

### Additional information

If useful, I can provide a smaller patch proposal focused specifically on `createTranscriptUpdateBroadcastHandler(...)` as a stopgap mitigation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions