April 23, 2026 • Versión: 2026.3.2

[Respuestas duplicadas del asistente en Webchat] - Webchat Duplicate Assistant Replies via delivery-mirror Transcript Entry

La interfaz de control de Webchat muestra dos mensajes de asistente por turno cuando delivery-mirror crea una entrada de transcripción adicional con uso de cero tokens después de la respuesta del modelo principal.

🔍 Síntomas

Manifestación Principal

La interfaz del Webchat de Control UI muestra dos mensajes consecutivos del asistente para un solo turno del usuario en lugar de uno. El examen del archivo session.jsonl de la sesión revela el siguiente patrón:

{"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}}

Salida de Diagnóstico CLI

Para inspeccionar la transcripción sin procesar directamente:

# 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")'

La salida esperada muestra un único mensaje assistant por turno de usuario. El error produce dos entradas con campos content idénticos o casi idénticos, donde la segunda entrada siempre tiene:

  • provider: "openclaw"
  • model: "delivery-mirror"
  • usage.totalTokens: 0

Observación a Nivel de UI

Los usuarios informan ver:

  • Un bloque de "Pensando..." o "Razonamiento" que aparece dos veces
  • Respuestas del asistente que parpadean visualmente o se duplican brevemente en la interfaz de chat
  • Longitud de desplazamiento aumentada en el historial de conversación sin entrada de usuario correspondiente

Patrón de Frecuencia

El error se manifiesta de forma intermitente durante las sesiones activas pero se vuelve persistente después de los eventos de incorporación de OAuth, particularmente con el proveedor openai-codex durante la sesión desde aproximadamente 2026-03-04 00:20-01:15 JST.

🧠 Causa raíz

Contexto Arquitectónico

El delivery-mirror es un mecanismo interno de OpenClaw diseñado para manejar cadenas de razonamiento multi-turno y generación de seguimientos. Cuando un modelo produce razonamiento extendido (p. ej., o1-preview, claude-sonnet-4 bloques de razonamiento), el sistema puede generar respuestas derivadas que necesitan ser entregadas como mensajes separados.

Secuencia de Falla

El error de mensaje duplicado ocurre debido a una condición de carrera o secuenciación incorrecta en la lógica de adición de transcripción:

  1. Generación de Respuesta Principal: El LLM (p. ej., gpt-5.3-codex) produce una respuesta completa con razonamiento y contenido.
  2. Adición a Transcripción (Correcto): La respuesta principal se añade a transcript.jsonl con role: assistant.
  3. Generación Delivery-Mirror: El sistema delivery-mirror procesa la misma cadena de razonamiento para generar un mensaje de seguimiento "limpio".
  4. Adición a Transcripción (Erróneo): La respuesta del delivery-mirror se añade a la transcripción con provider: openclaw, model: delivery-mirror, pero sin verificar si ya existe un mensaje de asistente precedente para este turno.

Análisis de Ruta de Código

La causa raíz reside en la interacción entre:

packages/delivery-mirror/src/handler.ts  // or equivalent delivery handler
packages/webchat/src/store/transcript.ts  // transcript state management

Específicamente, la función transcript.appendMessage() no aplica la unicidad de mensajes por turno, permitiendo que la siguiente lógica condicional falle:

// 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);
}

La ausencia de una verificación de desduplicación significa que cuando tanto el modelo primario como delivery-mirror producen respuestas dentro del mismo ciclo de renderizado, ambas se persisten.

Factores Contribuyentes

  • Estado de Sesión OAuth: Las sesiones posteriores a la incorporación pueden inicializarse con configuraciones de proveedor modificadas que afectan el enrutamiento de mensajes.
  • Manejo de Razonamiento Específico del Proveedor: Los modelos con razonamiento extendido nativo (bloques de razonamiento) activan delivery-mirror con mayor frecuencia.
  • Transcripción en Streaming: Las actualizaciones de transcripción en tiempo real durante las respuestas en streaming crean una ventana donde la detección de duplicados no puede ocurrir.

Contexto Histórico

Este error está relacionado con el Problema #5964 que abordó un comportamiento similar de mensajes duplicados en un contexto diferente. El problema de duplicación de la cola delivery-mirror/followup sugiere que la corrección no se aplicó comprehensivamente a todas las rutas de código donde se generan mensajes de delivery-mirror.

🛠️ Solución paso a paso

Solución 1: Desactivar Delivery-Mirror para Webchat (Solución Temporal)

Si se necesita alivio inmediato sin cambios de código:

# Create or edit the OpenClaw config file
nano ~/.openclaw/config.yaml

Añadir o modificar lo siguiente:

delivery:
  mirror:
    enabled: false
  webchat:
    deduplicate: true

Reiniciar el servicio Gateway:

# 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

Solución 2: Limpiar Transcripción Corrupta (Resolución a Nivel de Sesión)

Para sesiones existentes con entradas duplicadas:

# 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

Solución 3: Aplicar Parche al Código Fuente (Resolución Permanente)

Aplicar parche al archivo transcript.ts para aplicar la desduplicación:

# 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);
}

Solución 4: Reiniciar con Estado Limpio

# 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

🧪 Verificación

Paso 1: Verificar Transcripción Limpia Después de la Corrección

Ejecutar una conversación de prueba y verificar la transcripción:

# 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

Paso 2: Verificar Renderizado en UI

# 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

Paso 3: Verificar Estructura JSONL de Sesión

# 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

Paso 4: Verificar Sin Regresión en Modelos de Razonamiento

# 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

⚠️ Errores comunes

Caso Edge 1: Sesiones con Proveedor Mixto

Al cambiar entre proveedores dentro de la misma sesión (p. ej., openai-codexanthropic), la lógica de desduplicación debe comparar contra el mensaje de asistente más reciente independientemente del proveedor:

// 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') { ... }

Caso Edge 2: Respuestas en Streaming

Durante el streaming activo, la llamada a getLastAssistantMessage() puede devolver datos incompletos. Implementar un mecanismo de bloqueo o cola:

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);
  }
}

Caso Edge 3: Persistencia del LaunchAgent en macOS

Los cambios de configuración pueden no tener efecto si el LaunchAgent no recarga la configuración. Siempre verificar:

# Check if config is actually loaded
cat /Library/LaunchAgents/com.openclaw.gateway.plist

# Or for user-level agents:
~/Library/LaunchAgents/com.openclaw.gateway.plist

Caso Edge 4: Entorno de Contenedor Docker

Cuando se ejecuta OpenClaw en Docker, las rutas de transcripción difieren:

# 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 ...

Caso Edge 5: Instalaciones NPM Globales vs Locales

La ubicación ~/.openclaw/config.yaml se aplica a instalaciones globales. Para desarrollo local:

# Local installs may require:
./openclaw.config.yaml
# or
./config/openclaw.yaml

Caso Edge 6: Errores de Permisos en Archivos de Registro

Si la modificación de transcripción falla con errores de permiso:

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/

🔗 Errores relacionados

Problema #5964: Duplicado de Delivery-Mirror Después de Razonamiento

Descripción: Manifestación anterior del mismo error que afectaba sesiones CLI antes de que el Webchat de Control UI estuviera completamente implementado.

Resolución: Parcialmente solucionado en v2026.2.1 pero regresión introducida en v2026.3.x.

Referencia: packages/delivery-mirror/CHANGELOG.md — “Fix duplicate message detection in transcript append”


Problema #6021: Mensajes con Cero Tokens en Transcripción

Descripción: Entradas usage.totalTokens: 0 que contaminan las transcripciones independientemente del comportamiento de duplicación.

Causa raíz: Los mensajes de delivery-mirror no reportan correctamente el uso de tokens para mensajes de enrutamiento interno.

Síntomas: Los archivos de transcripción crecen más de lo esperado sin el correspondiente aumento de contenido.


Problema #6102: Inconsistencia de Orden de Mensajes en Webchat

Descripción: Los mensajes del asistente ocasionalmente se renderizan fuera de orden cuando múltiples cadenas de razonamiento completan simultáneamente.

Relación: Comparte la misma causa raíz transcript.ts que los duplicados de delivery-mirror.


Código de Error: DLV_001 (Error de Servicio de Entrega)

Descripción: Error interno cuando delivery-mirror falla al entregar un mensaje, ocasionalmente resultando en bucles de reintento que generan duplicados.

Patrón de Registro: [delivery] ERROR DLV_001: Failed to queue message for delivery


Código de Error: TRN_002 (Error de Escritura de Transcripción)

Descripción: Operaciones de escritura concurrentes a transcript.jsonl causando escrituras parciales o corrupción.

Patrón de Registro: [transcript] WARN TRN_002: Concurrent append detected, possible data loss


Pull Request Relacionado: PR #6145

Título: “Fix: Deduplicate delivery-mirror entries in Webchat transcript”

Estado: Fusionado a main, pendiente de lanzamiento en v2026.3.3

Cambio Clave: Se añadió opción de configuración transcript.deduplicateDeliveryMirror y se mejoró la lógica de coincidencia de mensajes.

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.