April 23, 2026 • Version: 2026.3.2

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:

  1. Primäre Antwortgenerierung: Das LLM (z.B. gpt-5.3-codex) produziert eine vollständige Antwort mit Reasoning und Inhalt.
  2. Protokoll-Append (Korrekt): Die primäre Antwort wird mit role: assistant an transcript.jsonl angehängt.
  3. Delivery-Mirror-Generierung: Das Delivery-Mirror-System verarbeitet dieselbe Reasoning-Kette, um eine "saubere" Followup-Nachricht zu generieren.
  4. Protokoll-Append (Fehlerhaft): Die Delivery-Mirror-Antwort wird mit provider: openclaw, model: delivery-mirror angehä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.yaml

Fü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/gateway

Lö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.jsonl

Lö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:0

Schritt 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-codexanthropic) 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.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.