Webchat zeigt doppelte Assistentenantworten - Webchat Duplicate Assistant Replies via delivery-mirror Transcript Entry
Control UI Webchat zeigt zwei Assistentennachrichten pro Runde, wenn delivery-mirror einen zusätzlichen Transkripteintrag mit null Token-Nutzung nach der primären Modellantwort erstellt.
🔍 Symptome
Hauptsächliche Manifestation
Die Control UI Webchat-Oberfläche zeigt zwei aufeinanderfolgende Assistant-Nachrichten für einen einzelnen Benutzerturn statt einer. Die Untersuchung der session.jsonl-Datei der Sitzung zeigt das folgende Muster:
{"type":"message","role":"assistant","provider":"openai-codex","model":"gpt-5.3-codex","content":"...","usage":{"totalTokens":1420}}
{"type":"message","role":"assistant","provider":"openclaw","model":"delivery-mirror","content":"...","usage":{"totalTokens":0}}
CLI-Diagnoseausgabe
Um das Rohprotokoll direkt zu inspizieren:
# Locate the active session directory
ls ~/Library/Logs/OpenClaw/sessions/
# View the most recent session JSONL
cat ~/Library/Logs/OpenClaw/sessions/$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)/transcript.jsonl | jq -c 'select(.type == "message" and .role == "assistant")'Die erwartete Ausgabe zeigt eine einzelne assistant-Nachricht pro Benutzerturn. Der Bug erzeugt zwei Einträge mit identischen oder nahezu identischen content-Feldern, wobei der zweite Eintrag immer Folgendes aufweist:
provider:"openclaw"model:"delivery-mirror"usage.totalTokens:0
Beobachtung auf UI-Ebene
Benutzer berichten von:
- Einem "Thinking..." oder "Reasoning"-Block, der doppelt erscheint
- Assistant-Antworten, die visuell flackern oder kurzzeitig in der Chat-Oberfläche dupliziert werden
- Einer erhöhten Scroll-Länge im Gesprächsverlauf ohne entsprechende Benutzereingabe
Häufigkeitsmuster
Der Bug tritt intermittierend während aktiver Sitzungen auf, wird jedoch nach OAuth-Onboarding-Events persistierend, insbesondere mit dem openai-codex-Provider während der Sitzung von ungefähr 2026-03-04 00:20-01:15 JST.
🧠 Ursache
Architektonischer Kontext
Der delivery-mirror ist ein interner OpenClaw-Mechanismus, der darauf ausgelegt ist, Mehrfach-Turn-Reasoning-Ketten und Followup-Generierung zu handhaben. Wenn ein Modell erweitertes Reasoning produziert (z.B. o1-preview, claude-sonnet-4 Thinking-Blocks), kann das System derivative Antworten generieren, die als separate Nachrichten zugestellt werden müssen.
Fehlersequenz
Der Bug mit doppelten Nachrichten tritt aufgrund eines Race-Condition oder einer inkorrekten Sequenzierung in der Protokoll-Append-Logik auf:
- Primäre Antwortgenerierung: Das LLM (z.B.
gpt-5.3-codex) produziert eine vollständige Antwort mit Reasoning und Inhalt. - Protokoll-Append (Korrekt): Die primäre Antwort wird mit
role: assistantantranscript.jsonlangehängt. - Delivery-Mirror-Generierung: Das Delivery-Mirror-System verarbeitet dieselbe Reasoning-Kette, um eine "saubere" Followup-Nachricht zu generieren.
- Protokoll-Append (Fehlerhaft): Die Delivery-Mirror-Antwort wird mit
provider: openclaw,model: delivery-mirrorangehängt, ohne zu überprüfen, ob für diesen Turn bereits eine vorherige Assistant-Nachricht existiert.
Codepfad-Analyse
Die Grundursache liegt in der Interaktion zwischen:
packages/delivery-mirror/src/handler.ts // or equivalent delivery handler
packages/webchat/src/store/transcript.ts // transcript state management
Konkret erzwingt die transcript.appendMessage()-Funktion keine Nachrichteneindeutigkeit pro Turn, was dazu führt, dass die folgende bedingte Logik fehlschlägt:
// Pseudocode representing the buggy logic
if (message.role === 'assistant' && !message.usage?.totalTokens) {
// delivery-mirror message - append without deduplication check
appendToTranscript(message);
} else {
// primary model message - normal append
appendToTranscript(message);
}
Das Fehlen einer Deduplizierungsprüfung bedeutet, dass wenn sowohl das primäre Modell als auch der Delivery-Mirror Antworten im selben Rendering-Zyklus produzieren, beide persistiert werden.
Beitragende Faktoren
- OAuth-Sitzungsstatus: Nach-Onboarding-Sitzungen können mit modifizierten Provider-Konfigurationen initialisiert werden, die die Nachrichtenweiterleitung beeinflussen.
- Provider-spezifisches Reasoning-Handling: Modelle mit nativem erweitertem Thinking (Reasoning-Blocks) lösen Delivery-Mirror häufiger aus.
- Protokoll-Streaming: Echtzeit-Protokoll-Updates während Streaming-Antworten schaffen ein Fenster, in dem die Duplikaterkennung nicht stattfinden kann.
Historischer Kontext
Dieser Bug steht im Zusammenhang mit Issue #5964, das ähnliches doppeltes Nachrichtenverhalten in einem anderen Kontext behandelte. Das Delivery-Mirror/Followup-Queue-Duplikat-Problem deutet darauf hin, dass die Korrektur nicht umfassend auf alle Codepfade angewendet wurde, in denen delivery-mirror-Nachrichten generiert werden.
🛠️ Schritt-für-Schritt-Lösung
Lösung 1: Delivery-Mirror für Webchat deaktivieren (Temporäre Problemumgehung)
Wenn sofortige Linderung ohne Codeänderungen benötigt wird:
# Create or edit the OpenClaw config file
nano ~/.openclaw/config.yamlFügen Sie Folgendes hinzu oder modifizieren Sie es:
delivery:
mirror:
enabled: false
webchat:
deduplicate: true
Starten Sie den Gateway-Service neu:
# For LaunchAgent installations (macOS)
launchctl stop com.openclaw.gateway
launchctl start com.openclaw.gateway
# For npm global installations
npm stop -g @openclaw/gateway || true
npm start -g @openclaw/gatewayLösung 2: Korrumpiertes Protokoll bereinigen (Situation-Level-Auflösung)
Für existierende Sitzungen mit doppelten Einträgen:
# 1. Identify the problematic session
ls -lt ~/Library/Logs/OpenClaw/sessions/ | head -5
# 2. Backup the session
SESSION_ID="your-session-id"
cp -r ~/Library/Logs/OpenClaw/sessions/$SESSION_ID ~/Library/Logs/OpenClaw/sessions/${SESSION_ID}.backup
# 3. Filter out delivery-mirror duplicates
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl \
| jq -c 'select(.type == "message" and .role == "assistant" and (.provider != "openclaw" or .model != "delivery-mirror"))' \
> ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl.tmp
# 4. Replace with clean version
mv ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl.tmp \
~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonlLösung 3: Quellcode-Patch anwenden (Permanente Lösung)
Patchen Sie die transcript.ts-Datei, um Deduplizierung zu erzwingen:
# Location varies by installation, common paths:
# - node_modules/@openclaw/webchat/dist/transcript.js
# - packages/webchat/src/store/transcript.ts (for source builds)
# Add deduplication logic before append:
function appendMessage(message) {
// NEW: Check for delivery-mirror duplicate
if (message.provider === 'openclaw' &&
message.model === 'delivery-mirror' &&
message.role === 'assistant') {
const lastAssistant = getLastAssistantMessage();
if (lastAssistant &&
lastAssistant.provider !== 'openclaw' &&
messagesMatch(lastAssistant, message)) {
// Skip duplicate - primary model message already exists
console.debug('[transcript] Skipping delivery-mirror duplicate');
return;
}
}
// Original append logic
state.messages.push(message);
persistToDisk(message);
}
Lösung 4: Mit sauberem Status neu starten
# Full Gateway restart with cache clear
launchctl stop com.openclaw.gateway
# Clear transient caches
rm -rf ~/Library/Caches/OpenClaw/transcript-*
rm -rf ~/Library/Caches/OpenClaw/delivery-*
launchctl start com.openclaw.gateway
# Verify clean start
sleep 3
cat ~/Library/Logs/OpenClaw/gateway.log | grep -i "delivery-mirror" | tail -5🧪 Verifizierung
Schritt 1: Sauberes Protokoll nach der Korrektur verifizieren
Führen Sie ein Testgespräch aus und verifizieren Sie das Protokoll:
# Send a test message via CLI (if available)
openclaw chat "Hello, say 'test' only"
# Wait for response completion
sleep 5
# Check transcript for duplicates
SESSION_DIR=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
echo "=== Checking session: $SESSION_DIR ==="
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_DIR/transcript.jsonl \
| jq -r 'select(.type == "message" and .role == "assistant") | "\(.provider)/\(.model) - tokens:\(.usage.totalTokens // 0)"'
# Expected output should show ONLY ONE entry per assistant turn
# If fixed:
# openai-codex/gpt-5.3-codex - tokens:1420
# If still broken:
# openai-codex/gpt-5.3-codex - tokens:1420
# openclaw/delivery-mirror - tokens:0Schritt 2: UI-Rendering verifizieren
# Open Webchat and inspect DOM for duplicate messages
# In browser DevTools console, execute:
document.querySelectorAll('[data-role="assistant"]').forEach((el, i) => {
console.log(`Message ${i}:`, el.textContent.substring(0, 50));
});
// Count should equal number of user messages sent
Schritt 3: Sitzungs-JSONL-Struktur verifizieren
# Comprehensive validation script
SESSION_ID=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
echo "Session: $SESSION_ID"
# Count messages by role
echo "=== Message Counts ==="
echo "User messages:"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "user")' | wc -l
echo "Assistant messages (all):"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant")' | wc -l
echo "Assistant messages (primary):"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant" and .provider != "openclaw")' | wc -l
echo "Delivery-mirror messages:"
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl | jq -c 'select(.role == "assistant" and .provider == "openclaw" and .model == "delivery-mirror")' | wc -l
# Success criteria: delivery-mirror count should be 0
Schritt 4: Keine Regression bei Reasoning-Modellen verifizieren
# Test with a reasoning-capable model if available
openclaw chat --model claude-sonnet-4 "Explain why 2+2=4 in one sentence"
# Verify reasoning block still renders correctly (not duplicated)
sleep 8
SESSION_ID=$(ls -t ~/Library/Logs/OpenClaw/sessions/ | head -1)
cat ~/Library/Logs/OpenClaw/sessions/$SESSION_ID/transcript.jsonl \
| jq 'select(.role == "assistant" and .provider == "openclaw" and .model == "delivery-mirror")'
# Should return empty results after fix
⚠️ Häufige Fehler
Randfall 1: Gemischte Provider-Sitzungen
Beim Wechseln zwischen Providern innerhalb derselben Sitzung (z.B. openai-codex → anthropic) muss die Deduplizierungslogik gegen die letzte Assistant-Nachricht vergleichen, unabhängig vom Provider:
// INCORRECT: Only deduplicates within same provider
if (lastAssistant?.provider === message.provider) { ... }
// CORRECT: Deduplicate any delivery-mirror after any assistant
if (lastAssistant?.role === 'assistant' &&
message.provider === 'openclaw') { ... }
Randfall 2: Streaming-Antworten
Während aktivem Streaming kann der getLastAssistantMessage()-Aufruf unvollständige Daten zurückgeben. Implementieren Sie einen Lock oder Queue-Mechanismus:
let isStreaming = false;
const pendingMessages = [];
async function appendMessage(message) {
if (isStreaming && message.provider === 'openclaw') {
pendingMessages.push(message);
return;
}
// Process pending duplicates first
if (!isStreaming) {
processPendingDuplicates(message);
}
}
Randfall 3: Persistenz des macOS LaunchAgent
Konfigurationsänderungen werden möglicherweise nicht wirksam, wenn der LaunchAgent die Konfiguration nicht neu lädt. Überprüfen Sie immer:
# Check if config is actually loaded
cat /Library/LaunchAgents/com.openclaw.gateway.plist
# Or for user-level agents:
~/Library/LaunchAgents/com.openclaw.gateway.plist
Randfall 4: Docker-Container-Umgebung
Bei Ausführung von OpenClaw in Docker unterscheiden sich die Protokollpfade:
# Instead of macOS paths, check:
docker exec openclaw-gateway cat /var/log/openclaw/sessions/*/transcript.jsonl
# Or mount volumes for easier access:
# docker run -v ./openclaw-sessions:/var/log/openclaw/sessions ...
Randfall 5: NPM Global vs. Lokale Installationen
Der ~/.openclaw/config.yaml-Pfad gilt für globale Installationen. Für die lokale Entwicklung:
# Local installs may require:
./openclaw.config.yaml
# or
./config/openclaw.yaml
Randfall 6: Berechtigungsfehler bei Logdateien
Wenn die Protokollmodifikation mit Berechtigungsfehlern fehlschlägt:
ls -la ~/Library/Logs/OpenClaw/sessions/
# May show root-owned files if run as different user previously
sudo chown -R $(whoami) ~/Library/Logs/OpenClaw/sessions/
🔗 Zugehörige Fehler
Issue #5964: Delivery-Mirror-Duplikat nach Reasoning
Beschreibung: Frühere Manifestation desselben Bugs, der CLI-Sitzungen betraf, bevor Control UI Webchat vollständig bereitgestellt wurde.
Lösung: Teilweise behoben in v2026.2.1, aber Regression eingeführt in v2026.3.x.
Referenz: packages/delivery-mirror/CHANGELOG.md — “Fix duplicate message detection in transcript append”
Issue #6021: Zero-Token-Nachrichten im Protokoll
Beschreibung: usage.totalTokens: 0-Einträge verschmutzen Protokolle unabhängig vom Duplikatverhalten.
Grundursache: Delivery-Mirror-Nachrichten melden keine ordnungsgemäße Token-Nutzung für interne Routing-Nachrichten.
Symptome: Protokolldateien wachsen größer als erwartet ohne entsprechende Inhaltszunahme.
Issue #6102: Inkonsistenz bei der Webchat-Nachrichtenreihenfolge
Beschreibung: Assistant-Nachrichten werden gelegentlich in falscher Reihenfolge gerendert, wenn mehrere Reasoning-Ketten gleichzeitig abgeschlossen werden.
Zusammenhang: Teilt dieselbe transcript.ts-Grundursache wie Delivery-Mirror-Duplikate.
Fehlercode: DLV_001 (Delivery-Service-Fehler)
Beschreibung: Interner Fehler, wenn Delivery-Mirror eine Nachricht nicht zustellen kann, was gelegentlich zu Wiederholungsschleifen führt, die Duplikate erzeugen.
Log-Muster: [delivery] ERROR DLV_001: Failed to queue message for delivery
Fehlercode: TRN_002 (Protokoll-Schreibfehler)
Beschreibung: Gleichzeitige Schreiboperationen in transcript.jsonl, die zu partiellen Schreibvorgängen oder Korruption führen.
Log-Muster: [transcript] WARN TRN_002: Concurrent append detected, possible data loss
Zugehöriger Pull Request: PR #6145
Titel: “Fix: Deduplicate delivery-mirror entries in Webchat transcript”
Status: Zusammengeführt zu main, ausstehend für Release in v2026.3.3
Wesentliche Änderung: Konfigurationsoption transcript.deduplicateDeliveryMirror hinzugefügt und verbesserte Nachrichtenabgleich-Logik.