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 Probada | Resultado 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:
- Contenedor Sidecar del Navegador: Un contenedor Docker ejecutando Chromium con CDP habilitado, adjunto a la red Docker `openclaw-sandbox-browser`.
- 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 Cliente | 127.0.0.1 Se Resuelve A | ¿Contenedor del Navegador Alcanzable? |
|---|---|---|
| Máquina host | Interfaz loopback del host | Sí (mediante enlace de puerto) |
| Contenedor del agente | Loopback del propio contenedor del agente | No (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:
- El agente se está ejecutando dentro de un contenedor Docker
- El contenedor del agente comparte la misma red Docker que el contenedor del navegador
- 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 Error | Descripción |
|---|---|
Chrome CDP websocket for profile “X” is not reachable after start | Síntoma principal; indica timeout de conexión CDP o rechazo |
PortInUseError: Port 9222 is already in use | Ocurre cuando attachOnly: true no está establecido pero un navegador ya se está ejecutando |
Browser attachOnly is enabled and profile ‘X’ is not running | El probe de disponibilidad falla al detectar Chrome lanzado por contenedor |
Connection refused | Fallo a nivel de red cuando el host CDP no puede ser alcanzado |
ETIMEDOUT | Ocurre cuando el contenedor del agente no tiene acceso a la red al navegador |
Versiones Afectadas
- OpenClaw 2026.4.x: Confirmado afectado;
ensureSandboxBrowsertiene codificado127.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.