April 17, 2026 • Versión: 2026.4.5

El agente en sandbox no puede alcanzar el CDP del navegador — 127.0.0.1 codificado en ensureSandboxBrowser

Cuando el modo sandbox se establece en 'all', la conexión websocket de CDP falla porque ensureSandboxBrowser mapea el puerto CDP al host 127.0.0.1, que es inalcanzable desde el interior del contenedor del agente en sandbox de Docker.

🔍 Síntomas

Manifestación del Error Principal

Cuando un agente se ejecuta dentro de un contenedor Docker con agents.defaults.sandbox.mode: "all", cualquier invocación de la herramienta browser produce un fallo de conexión:

Error: Chrome CDP websocket for profile "openclaw" is not reachable after start.
    at BrowserTool._waitForChromeReady (node_modules/openclaw/dist/agents/tools/browser/index.js:XXXX:XX)
    at BrowserTool.open (node_modules/openclaw/dist/agents/tools/browser/index.js:XXXX:XX)
    at ...

Evidencia de Diagnóstico de Red

Desde dentro del contenedor de agente sandbox, las pruebas de conectividad revelan el problema fundamental:

# Attempting to reach the CDP port via host loopback
$ curl -v http://127.0.0.1:9222
curl: (7) Failed to connect to 127.0.0.1 port 9222 after 0ms: Connection refused

# Confirming 127.0.0.1 is the container's own loopback
$ ip route show
default via 172.18.0.1 dev eth0
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.2

# The browser container exists on the Docker network but is unreachable via 127.0.0.1
$ ping -c 1 openclaw-sandbox-browser-abc123
PING openclaw-sandbox-browser-abc123 (172.18.0.3) 56(84) bytes of data.
64 bytes from openclaw-sandbox-browser-abc123 (172.18.0.3): icmp_seq=1 ttl=64 time=0.1 ms

Configuración que Activa el Problema

La siguiente configuración mínima reproduce el fallo:

{
  "agents": {
    "defaults": {
      "sandbox": {
        "mode": "all",
        "scope": "agent",
        "workspaceAccess": "rw"
      }
    }
  },
  "tools": {
    "sandbox": {
      "tools": {
        "alsoAllow": ["browser"]
      }
    }
  }
}

Fallos de Soluciones Alternativas Documentados

Configuración ProbadaResultado Observado
Default (sin docker.network, network: none)El agente no tiene red; CDP inaccesible
sandbox.docker.network: "openclaw-sandbox-browser"Sigue fallando—el código usa 127.0.0.1 no el DNS del contenedor
sandbox.docker.extraHosts: ["host.docker.internal:host-gateway"]127.0.0.1 sigue resolviendo al loopback del contenedor
browser.profiles.remote-chrome.cdpUrl = "ws://127.0.0.1:9222" con attachOnly: true"Browser attachOnly is enabled and profile 'remote-chrome' is not running."
Mismo sin attachOnly"PortInUseError: Port 9222 is already in use."

🧠 Causa Raíz

Visión General de la Arquitectura

La arquitectura de automatización del navegador OpenClaw involucra dos contextos de ejecución distintos:

  1. Contenedor Sidecar del Navegador: Un contenedor Docker ejecutando Chromium con CDP habilitado, adjunto a la red Docker `openclaw-sandbox-browser`.
  2. Contenedor Sandbox del Agente: Un contenedor Docker opcional ejecutando el código del agente, también potencialmente adjunto a la red `openclaw-sandbox-browser`.

La Estrategia Defectuosa de Mapeo de Puertos

La función ensureSandboxBrowser en src/agents/sandbox/browser.ts crea el contenedor del navegador y publica el puerto CDP usando este patrón:

// Simplified representation of the problematic code path
async function ensureSandboxBrowser(config) {
  const browserContainer = await docker.createContainer({
    // ... container config ...
    HostConfig: {
      PortBindings: {
        "9222/tcp": [{ HostIp: "127.0.0.1", HostPort: cdpPort }]  // ← THE BUG
      }
    }
  });

  // Later, browser tool client dials this hardcoded address:
  const cdpUrl = `ws://127.0.0.1:${cdpPort}`;
  return { container: browserContainer, cdpUrl };
}

Por Qué 127.0.0.1 Falla Dentro de un Contenedor

La topología de red difiere según la ubicación del cliente:

Ubicación del Cliente127.0.0.1 Se Resuelve A¿Contenedor del Navegador Alcanzable?
Máquina hostInterfaz loopback del hostSí (mediante enlace de puerto)
Contenedor del agenteLoopback del propio contenedor del agenteNo (namespace de red separado)

El puerto del contenedor del navegador está vinculado a 127.0.0.1 en el namespace de red del host. El contenedor del agente tiene su propia interfaz loopback aislada—el tráfico hacia 127.0.0.1 desde dentro del contenedor del agente nunca sale de ese namespace.

La Lógica que Falta

El código carece de lógica condicional para detectar cuándo:

  1. El agente se está ejecutando dentro de un contenedor Docker
  2. El contenedor del agente comparte la misma red Docker que el contenedor del navegador
  3. La URL del CDP debería por lo tanto usar la resolución DNS interna de Docker

Las redes bridge definidas por el usuario de Docker proporcionan resolución DNS automática entre contenedores por sus nombres de contenedor. Si el contenedor del agente está en openclaw-sandbox-browser y el nombre del contenedor del navegador es openclaw-sandbox-browser-abc123, el agente puede alcanzar el navegador a través de ese nombre DNS en cualquier puerto.

Análisis del Camino del Código

agent.sandbox.run() └── ensureSandboxBrowser() ├── Creates browser container on openclaw-sandbox-browser network ├── Binds port to 127.0.0.1 (host only) └── Returns cdpUrl = “ws://127.0.0.1:9222”

browser-tool.client.connect() └── Attempts to connect to cdpUrl └── If agent is containerized: connection refused (container loopback)

🛠️ Solución Paso a Paso

Configuración Prerrequisito

Antes de aplicar la solución, asegúrese de que el sandbox del agente esté configurado para unirse a la red del contenedor del navegador. Agregue lo siguiente a su configuración de OpenClaw:

{
  "agents": {
    "defaults": {
      "sandbox": {
        "mode": "all",
        "scope": "agent",
        "workspaceAccess": "rw",
        "docker": {
          "network": "openclaw-sandbox-browser"
        }
      }
    }
  }
}

La Solución: Modificar ensureSandboxBrowser para Usar DNS del Contenedor

Ubique src/agents/sandbox/browser.ts y aplique el siguiente parche:

Antes (problemático):

async function ensureSandboxBrowser(config) {
  const containerName = generateContainerName('openclaw-sandbox-browser');
  
  const container = await docker.createContainer({
    name: containerName,
    Image: config.browserImage,
    HostConfig: {
      NetworkMode: 'openclaw-sandbox-browser',
      PortBindings: {
        "9222/tcp": [{ HostIp: "127.0.0.1", HostPort: String(cdpPort) }]
      }
    }
  });

  const cdpUrl = `ws://127.0.0.1:${cdpPort}`;
  return { container, cdpUrl, containerName };
}

Después (corregido):

async function ensureSandboxBrowser(config, agentContext) {
  const containerName = generateContainerName('openclaw-sandbox-browser');
  const cdpPort = config.cdpPort || 9222;
  
  // Determine the correct CDP host based on runtime context
  let cdpHost = "127.0.0.1";
  
  if (agentContext?.isSandboxed && agentContext?.dockerNetwork === 'openclaw-sandbox-browser') {
    // Agent container shares the browser network; use internal DNS
    cdpHost = containerName;
    // No host port binding needed when agent and browser share a network
    logger.info(`Sandboxed agent detected; using internal DNS (${cdpHost}) for CDP`);
  }

  const createOptions = {
    name: containerName,
    Image: config.browserImage,
    HostConfig: {
      NetworkMode: 'openclaw-sandbox-browser',
      // Only bind to host port if agent is not on the same network
      PortBindings: cdpHost === "127.0.0.1" 
        ? { "9222/tcp": [{ HostIp: "127.0.0.1", HostPort: String(cdpPort) }] }
        : {}  // Internal network access requires no host binding
    }
  };

  const container = await docker.createContainer(createOptions);
  const cdpUrl = `ws://${cdpHost}:${cdpPort}`;
  
  return { container, cdpUrl, containerName };
}

Alternativa: Configuración Basada en Variables de Entorno (Solución Sin Código)

Si modificar el código fuente no es factible, configure la herramienta del navegador para usar el endpoint interno del contenedor estableciendo la URL del CDP directamente:

{
  "browser": {
    "profiles": {
      "openclaw": {
        "cdpUrl": "ws://openclaw-sandbox-browser-{{CONTAINER_ID}}:9222",
        "launchOptions": {
          "args": ["--disable-web-security"]
        }
      }
    }
  }
}

Nota: Esto requiere conocer el nombre del contenedor del navegador en el momento de la configuración. El nombre del contenedor se genera dinámicamente; use un prefijo de nomenclatura consistente o inspeccione con docker ps --filter name=openclaw-sandbox-browser.

Verificación de la Red Docker

Asegúrese de que la red exista antes de iniciar OpenClaw:

# Create the shared network if it doesn't exist
$ docker network create openclaw-sandbox-browser 2>/dev/null || true

# Verify network configuration
$ docker network inspect openclaw-sandbox-browser --format '{{range .IPAM.Config}}Subnet: {{.Subnet}}{{end}}'
Subnet: 172.18.0.0/16

🧪 Verificación

Paso 1: Verificar que el Contenedor del Navegador Inicia Exitosamente

$ docker ps -a --filter "name=openclaw-sandbox-browser" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

NAMES                        STATUS          PORTS
openclaw-sandbox-browser-abc123   Up 2 minutes   0.0.0.0:9222->9222/tcp

Paso 2: Confirmar que el Contenedor del Agente Está en la Misma Red

$ docker inspect openclaw-agent-xyz789 --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}'
openclaw-sandbox-browser

Paso 3: Probar Conectividad CDP desde el Contenedor del Agente

$ docker exec openclaw-agent-xyz789 curl -s http://openclaw-sandbox-browser-abc123:9222/json/version | head -1

{
  "Browser": "Chromium/120.0.6099.109",
  "Protocol-Version": "1.3",
  "User-Agent": "Mozilla/5.0 ...",
  "V8-Version": "12.0.6099.109",
  "WebKit-Version": "537.36 ..."
}

Paso 4: Verificar el Upgrade de WebSocket

$ docker exec openclaw-agent-xyz789 curl -v \
  --no-buffer \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://openclaw-sandbox-browser-abc123:9222 \
  2>&1 | grep -E "(HTTP|Upgrade: websocket)"

< HTTP/1.1 101 Switching Protocols
< Upgrade: websocket
< Connection: Upgrade

Paso 5: Prueba Funcional con OpenClaw

Ejecute una tarea basada en navegador a través de la CLI o API:

$ openclaw run --task "Navigate to example.com and return the page title"

[INFO] Sandbox mode: all
[INFO] Starting agent in container...
[INFO] Browser container resolved via internal DNS: openclaw-sandbox-browser-abc123
[INFO] CDP connected successfully
[INFO] Page title: "Example Domain"
[SUCCESS] Task completed in 4.2s

Paso 6: Confirmar que el Acceso desde el Host Sigue Funcionando

Desde la máquina host (no dentro de ningún contenedor):

$ curl -s http://127.0.0.1:9222/json/version | head -1

{
  "Browser": "Chromium/120.0.6099.109",

Esperado: Tanto el host (127.0.0.1) como el DNS interno del contenedor (openclaw-sandbox-browser-*) son accesibles.

⚠️ Errores Comunes

1. Desajuste del Modo de Red

El sandbox del agente debe unirse explícitamente a la red openclaw-sandbox-browser. Si se omite, el agente usa por defecto network: none (sin red) o la red bridge por defecto.

// WRONG: Missing network configuration
"sandbox": {
  "mode": "all"
  // Agent will have no network or wrong network
}

// CORRECT: Explicit network attachment
"sandbox": {
  "mode": "all",
  "docker": {
    "network": "openclaw-sandbox-browser"
  }
}

2. Condición de Carrera en el Inicio del Contenedor

El contenedor del navegador puede no estar completamente listo cuando el agente intenta la conexión CDP. Implemente una verificación de disponibilidad:

async function waitForCdpReady(host, port, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const response = await fetch(`http://${host}:${port}/json/version`);
      if (response.ok) return true;
    } catch (e) {
      await new Promise(r => setTimeout(r, 1000));
    }
  }
  throw new Error(`CDP not ready after ${maxAttempts} attempts`);
}

3. Nombres de Contenedor Dinámicos

Los nombres de los contenedores del navegador se generan con sufijos aleatorios. Use un prefijo de nomenclatura consistente para la resolución DNS:

// In browser.ts, prefer deterministic naming when possible:
const containerName = config.customName || `openclaw-sandbox-browser`;

Alternativamente, use el --network-alias de Docker para crear un alias estable:

HostConfig: {
  NetworkMode: 'openclaw-sandbox-browser',
  DNS: ['openclaw-sandbox-browser-alias']  // Use this as CDP host
}

4. Peculiaridades de Docker Desktop en macOS

En Docker Desktop con WSL2, el host adicional host.docker.internal se mapea a la VM WSL2, no al host real. El enfoque de DNS interno sigue funcionando correctamente ya que usa el DNS de contenedores de Docker.

5. Loopback IPv6

Algunos sistemas tienen ::1 (loopback IPv6) priorizado sobre 127.0.0.1 (IPv4). Asegúrese de que la URL del CDP use explícitamente IPv4:

cdpUrl = "ws://127.0.0.1:9222";  // Explicit IPv4
// NOT: "ws://localhost:9222";   // May resolve to ::1

6. Conflictos de Puerto en Entornos Compartidos

Si existen múltiples contenedores de navegador en el mismo host, asegúrese de usar puertos CDP distintos:

{
  "browser": {
    "profiles": {
      "openclaw": {
        "cdpPort": 29222  // Non-standard port to avoid conflicts
      }
    }
  }
}

🔗 Errores Relacionados

Problemas Conectados

  • #52662 — External CDP endpoint configuration: Propone exponer una opción de configuración `externalCdpEndpoint` para permitir adjuntar un navegador gestionado externamente. El modo `attachOnly` falla porque el probe de disponibilidad está basado en PID del host y no reconoce el Chrome lanzado por contenedor.
  • #58606 — Browser container starts but CDP port unreachable: Misma causa raíz que este problema pero enmarcado desde la perspectiva de exposición de puertos en lugar de la perspectiva de red del sandbox.
  • #64383 — Remove socat CDP intermediate layer: Discusión sobre eliminar la capa intermedia del proxy de puente CDP. Si se resuelve, simplificaría el modelo de red pero no aborda directamente la codificación rígida de 127.0.0.1.

Mensajes de Error Relacionados

Código/Mensaje de ErrorDescripción
Chrome CDP websocket for profile “X” is not reachable after startSíntoma principal; indica timeout de conexión CDP o rechazo
PortInUseError: Port 9222 is already in useOcurre cuando attachOnly: true no está establecido pero un navegador ya se está ejecutando
Browser attachOnly is enabled and profile ‘X’ is not runningEl probe de disponibilidad falla al detectar Chrome lanzado por contenedor
Connection refusedFallo a nivel de red cuando el host CDP no puede ser alcanzado
ETIMEDOUTOcurre cuando el contenedor del agente no tiene acceso a la red al navegador

Versiones Afectadas

  • OpenClaw 2026.4.x: Confirmado afectado; ensureSandboxBrowser tiene codificado 127.0.0.1
  • OpenClaw 2026.3.x: Probablemente afectado; mismo camino de código
  • OpenClaw 2026.5.x: Solución pendiente; parche esperado en la próxima versión menor

Estado de la Solución Alternativa

Hasta que se fusione la solución, la única solución alternativa completamente funcional es:

{
  "browser": {
    "enabled": false
  }
}

Esto deshabilita la herramienta del navegador por completo, recurriendo a web_fetch para todo el acceso web basado en HTTP. Esto es inadecuado para sitios que requieren ejecución de JavaScript o flujos interactivos de clic.

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.