Bug type
Regression (worked before, now fails)
Summary
The Control UI (webchat provider) records two assistant messages per LLM turn in the session JSONL file, causing duplicate responses in the conversation context and visible double-messages in the UI.
Steps to reproduce
- Start the OpenClaw gateway with the Control UI enabled (default)
- Open the dashboard at
http://127.0.0.1:<port>/#token=<token>
- Send any message via the Control UI chat input
- Observe the assistant response — it appears twice in the chat
Minimal repro: Any message sent via the Control UI triggers the issue. No special configuration required.
Expected behavior
Each LLM turn should produce exactly one assistant message entry in the session JSONL file, and the response should appear once in the Control UI.
Actual behavior
Each LLM turn produces two assistant message entries in the session JSONL file, approximately 400–900ms apart. The two entries:
- Have different
id and timestamp fields
- Have separate
usage and stopReason metadata
- Contain nearly identical content (differ by ~2 characters of leading whitespace — first has
\n\n prefix, second does not)
Because both entries are written to the session file, subsequent LLM turns see the duplicated assistant message in their context window. This causes the model to sometimes mirror the duplication pattern, producing visibly repeated text in responses.
This does NOT occur with the Telegram channel. In the same session, messages originating from Telegram produce exactly one assistant entry. The issue is isolated to the webchat/Control UI provider.
OpenClaw version
2026.3.2 (85377a2)
Operating system
Ubuntu 24.04.4 LTS (linux-surface 6.18.7-surface-1, x64)
Install method
npm global (~/.npm-global/lib/node_modules/openclaw)
Logs, screenshots, and evidence
### Quantified evidence from a single session
Analyzed session JSONL (`<session-id>.jsonl`):
| Message source | Consecutive assistant message pairs (duplicates) |
|---|---|
| Control UI (webchat) | **80** |
| Telegram | **3** (not true duplicates — these were multi-part messages split across tool calls) |
### Example duplicate pair from JSONL
[2026-03-08T00:56:25.107Z] assistant (len=69): "\n\nPerfect — right there in Starred, one click away. You're all set. 👍"
[2026-03-08T00:56:26.003Z] assistant (len=67): "Perfect — right there in Starred, one click away. You're all set. 👍"
Note: ~900ms apart, content differs only by leading `\n\n`.
Impact and severity
Severity: Medium
- Context pollution: Duplicate assistant messages inflate the context window, reducing effective context capacity and increasing token costs
- Model behavior degradation: The model sees its own duplicated responses in context and sometimes mirrors the pattern, producing visibly repeated text — degrading response quality
- User confusion: Users see double messages in the Control UI chat, creating the impression of a buggy or broken system
- Session integrity: Session JSONL files contain spurious entries that pollute session history and any downstream processing (memory search indexing, transcript exports, etc.)
Frequency: 100% — every single Control UI message triggers this. Not intermittent.
Context exhaustion: Duplicate recording doubles token consumption, causing sessions to hit the 200K context limit at 2× the normal rate. In testing, a session that would have been ~87K tokens on Telegram reached 174K on the Control UI due to this bug.
Cross-channel spam: Because dmScope: "main" shares a single session across all channels, duplicate messages recorded by the Control UI are also delivered to Telegram. During testing, single responses were delivered 4-6× to Telegram when the conversation originated from the Control UI. Switching to Telegram-native messages immediately produced clean single responses, confirming the duplication originates in the webchat provider's recording path, not the delivery pipeline.
Compounding feedback loop: The duplication escalates over the course of a session. The base bug always records 2× assistant messages per turn. However, as duplicates accumulate in the context window, the model begins pattern-matching against its own duplicated history and mirrors the repetition within its responses. This creates a feedback loop:
| Session stage |
Observed copies per response |
Cause |
| Early turns |
2× |
Base bug (webchat double-recording) |
| Mid session |
3–4× |
Model begins mirroring duplication pattern from context |
| Late session |
6–8× |
Feedback loop fully compounding — context saturated with duplicates |
This explains the variable duplication count observed across different messages. The escalation resets when the session is reset or when the user switches to a non-affected channel (e.g., Telegram), which breaks the feedback loop by producing clean single entries.
Not a config issue: Full config audit confirmed the duplication is not caused by dmScope, sendPolicy, or any other user-configurable setting. The webchat provider is built-in and has no user-facing configuration to control this behavior. The dmScope: "main" setting amplifies the blast radius (cross-channel delivery of duplicates) but is the correct configuration for a single-user deployment and should not be changed as a workaround.
Workaround: Use Telegram (or presumably any other channel) instead of the Control UI for chat. The Control UI is still usable for monitoring — only the chat input is affected.
Additional information
- The issue persists across gateway restarts and session resets
- Streaming is set to
"off" — unclear if streaming modes (partial, block) are also affected
- The duplicate does not appear to be a client-side rendering issue — the two entries exist in the server-side session JSONL file with distinct IDs and timestamps
- The webchat provider source is minified in the dist build, so the exact code path could not be traced from the installed package
Root cause (source-confirmed)
The duplicates are delivery-mirror entries — an intentional OpenClaw feature that records cross-channel message delivery in the session transcript. The bug is not that they're written, but that they're included in the LLM context window as regular assistant messages, creating the appearance of duplicated responses.
How it works (traced through source)
- User sends message via Control UI (webchat provider)
- LLM processes and responds → assistant message written to session JSONL by the session manager (provider:
anthropic, model: claude-opus-4-6)
- Response delivered back to Control UI via WebSocket dispatcher — no mirror written (same channel)
- Response ALSO delivered to Telegram (cross-channel, because
dmScope: "main" shares the session) → deliverOutboundPayloads() in deliver-*.js calls appendAssistantMessageToSessionTranscript() which writes a second assistant message with model: "delivery-mirror", provider: "openclaw", api: "openai-responses"
- Next LLM turn: the session manager loads ALL assistant messages from the JSONL — including delivery-mirror entries — into the context window. The LLM sees two near-identical assistant messages and begins mirroring the pattern.
Evidence from JSONL analysis
| Message source |
Anthropic (real) responses |
delivery-mirror entries |
Mirror ratio |
| Control UI (44 messages) |
101 |
62 |
0.61 per response |
| Telegram (29 messages) |
65 |
2 |
0.03 per response |
| System/cron (9 messages) |
44 |
0 |
0.00 per response |
This confirms:
- Control UI → high mirror count because responses are cross-delivered to Telegram
- Telegram → near-zero mirrors because webchat is not a routable channel (
isRoutableChannel() returns false for webchat/gateway)
- Cron → zero mirrors because internal channel is not routable
Source code references (OpenClaw 2026.3.2, build 85377a2)
sessions-DNn6Jbbx.js: appendAssistantMessageToSessionTranscript() — writes delivery-mirror entries
deliver-FEreRY0J.js: deliverOutboundPayloadsCore() — triggers mirror write when params.mirror && results.length > 0
pi-embedded-CtM2Mrrj.js: isRoutableChannel() — webchat returns false, so webchat-originated replies go through dispatcher.sendFinalReply() (no mirror), but cross-channel
Environment
- Node.js: v22.22.1
- Gateway auth: token mode
- Streaming: off (
channels.telegram.streaming: "off")
- Model: anthropic/claude-opus-4-6
Bug type
Regression (worked before, now fails)
Summary
The Control UI (webchat provider) records two assistant messages per LLM turn in the session JSONL file, causing duplicate responses in the conversation context and visible double-messages in the UI.
Steps to reproduce
http://127.0.0.1:<port>/#token=<token>Minimal repro: Any message sent via the Control UI triggers the issue. No special configuration required.
Expected behavior
Each LLM turn should produce exactly one assistant message entry in the session JSONL file, and the response should appear once in the Control UI.
Actual behavior
Each LLM turn produces two assistant message entries in the session JSONL file, approximately 400–900ms apart. The two entries:
idandtimestampfieldsusageandstopReasonmetadata\n\nprefix, second does not)Because both entries are written to the session file, subsequent LLM turns see the duplicated assistant message in their context window. This causes the model to sometimes mirror the duplication pattern, producing visibly repeated text in responses.
This does NOT occur with the Telegram channel. In the same session, messages originating from Telegram produce exactly one assistant entry. The issue is isolated to the webchat/Control UI provider.
OpenClaw version
2026.3.2 (85377a2)
Operating system
Ubuntu 24.04.4 LTS (linux-surface 6.18.7-surface-1, x64)
Install method
npm global (
~/.npm-global/lib/node_modules/openclaw)Logs, screenshots, and evidence
Impact and severity
Severity: Medium
Frequency: 100% — every single Control UI message triggers this. Not intermittent.
Context exhaustion: Duplicate recording doubles token consumption, causing sessions to hit the 200K context limit at 2× the normal rate. In testing, a session that would have been ~87K tokens on Telegram reached 174K on the Control UI due to this bug.
Cross-channel spam: Because
dmScope: "main"shares a single session across all channels, duplicate messages recorded by the Control UI are also delivered to Telegram. During testing, single responses were delivered 4-6× to Telegram when the conversation originated from the Control UI. Switching to Telegram-native messages immediately produced clean single responses, confirming the duplication originates in the webchat provider's recording path, not the delivery pipeline.Compounding feedback loop: The duplication escalates over the course of a session. The base bug always records 2× assistant messages per turn. However, as duplicates accumulate in the context window, the model begins pattern-matching against its own duplicated history and mirrors the repetition within its responses. This creates a feedback loop:
This explains the variable duplication count observed across different messages. The escalation resets when the session is reset or when the user switches to a non-affected channel (e.g., Telegram), which breaks the feedback loop by producing clean single entries.
Not a config issue: Full config audit confirmed the duplication is not caused by
dmScope,sendPolicy, or any other user-configurable setting. The webchat provider is built-in and has no user-facing configuration to control this behavior. ThedmScope: "main"setting amplifies the blast radius (cross-channel delivery of duplicates) but is the correct configuration for a single-user deployment and should not be changed as a workaround.Workaround: Use Telegram (or presumably any other channel) instead of the Control UI for chat. The Control UI is still usable for monitoring — only the chat input is affected.
Additional information
"off"— unclear if streaming modes (partial,block) are also affectedRoot cause (source-confirmed)
The duplicates are
delivery-mirrorentries — an intentional OpenClaw feature that records cross-channel message delivery in the session transcript. The bug is not that they're written, but that they're included in the LLM context window as regular assistant messages, creating the appearance of duplicated responses.How it works (traced through source)
anthropic, model:claude-opus-4-6)dmScope: "main"shares the session) →deliverOutboundPayloads()indeliver-*.jscallsappendAssistantMessageToSessionTranscript()which writes a second assistant message withmodel: "delivery-mirror",provider: "openclaw",api: "openai-responses"Evidence from JSONL analysis
This confirms:
isRoutableChannel()returns false for webchat/gateway)Source code references (OpenClaw 2026.3.2, build 85377a2)
sessions-DNn6Jbbx.js:appendAssistantMessageToSessionTranscript()— writesdelivery-mirrorentriesdeliver-FEreRY0J.js:deliverOutboundPayloadsCore()— triggers mirror write whenparams.mirror && results.length > 0pi-embedded-CtM2Mrrj.js:isRoutableChannel()— webchat returns false, so webchat-originated replies go throughdispatcher.sendFinalReply()(no mirror), but cross-channelEnvironment
channels.telegram.streaming: "off")