Un navegador en la nube con rate limit ya no mata a browser_exec


Configuraste un navegador en la nube para que Hermes capturara una captura de tu panel cada mañana y la dejara en un canal. Funcionó toda la semana y, una mañana, en lugar del gráfico recibes un error: returned no CDP endpoint, so Browser Use mode cannot drive it. La página está bien. Tu script está bien. El servicio de navegador en la nube te estaba aplicando un rate limit por un momento — y toda la tarea del navegador murió con él.

Lo más desconcertante: las herramientas integradas browser_* de Hermes siguieron funcionando en exactamente las mismas condiciones. Solo browser_exec se cayó.

Dos formas en las que el navegador se quedaba atascado

Ambos síntomas vienen de lo mismo. Una vez configurado un navegador en la nube (browser.cloud_provider, elegido entre browserbase, browser-use, camofox, nous o local), debería seguir existiendo un camino local disponible en la máquina — pero browser_exec no llegaba a él.

Uno: falla la creación de sesión, falla la llamada

Cuando el proveedor en la nube fallaba al crear la sesión, browser_exec fallaba directamente:

Cloud browser provider BrowserUseBrowserProvider returned no CDP endpoint,
so Browser Use mode cannot drive it. Switch to the built-in browser tools
for this provider.

La falla vista en producción fue un rate limit: la pasarela respondió 429 RATE_LIMIT_EXCEEDED con retryAfter 17 y limit 200.

La causa raíz es que browser_exec y las herramientas integradas no seguían el mismo camino. Las herramientas de navegador integradas ya recurren al Chromium local empaquetado cuando no se puede crear una sesión local, marcando la sesión como fallback_from_cloud. browser_exec solo leía cdp_url de esa sesión — y una sesión local no tiene cdp_url hasta que su Chromium arranca. Así que lanzaba el error de arriba y culpaba al proveedor.

Hay además un amplificador: la sesión fallida se queda en caché. Las llamadas posteriores con la misma session key seguían chocando con la misma sesión rota hasta que el reaper de inactividad la eliminaba. Un solo 429 podía arruinar los siguientes minutos de trabajo con el navegador.

El arreglo es deliberadamente pequeño. En tools/browser_use_cli.py::_resolve_backend_cdp, cuando la sesión del proveedor vuelve como fallback_from_cloud, se resuelve el endpoint CDP del Chromium empaquetado igual que lo hace el camino sin proveedor, y se registra el fallo del proveedor una sola vez. Un proveedor que devuelve una sesión sin CDP URL y sin fallback sigue fallando con el mismo mensaje — nada finge funcionar.

Dos: el navegador en la nube no alcanza tu localhost

El segundo síntoma es más habitual: tienes un servidor de desarrollo en esta máquina, pides http://localhost:3000 y obtienes:

Blocked: URL targets a private or internal address

browser_exec pre-escanea las URL de tu código por seguridad, pero llamaba a esa comprobación sin el veredicto del enrutado híbrido. Con cloud_provider definido, el backend cuenta como no local, así que toda dirección privada se rechazaba.

browser_navigate nunca tuvo este problema — enruta las URL privadas a un sidecar local. Dos caminos dentro de un mismo subsistema de navegador, dos comportamientos distintos; este cambio los alinea.

Qué URL va a dónde

Dirección que solicitas Quién la abre
https://example.com el navegador en la nube configurado
http://localhost:3000 sidecar local de Chromium bajo la clave de caché ::local
http://192.168.1.9 (LAN) sidecar local de Chromium
169.254.169.254 (metadatos de la nube) bloqueada incondicionalmente, sin backend
una llamada que mezcla URL públicas y privadas toda la llamada se ejecuta en local

Algunos detalles de implementación importan porque son críticos para la seguridad:

  • El predicado de enrutado _hybrid_routes_locally(url) ahora lo comparten browser_navigate y browser_exec, así que los dos no pueden divergir;
  • Cada URL del código se evalúa una sola vez, y ese único veredicto decide tanto la comprobación de seguridad como la ruta. Un host que resuelve a privado y luego se reasigna a una IP pública no puede relajar la comprobación de direcciones privadas y aun así acabar enviándose a la nube;
  • Una llamada que mezcla URL públicas y privadas se ejecuta enteramente en local — la parte privada nunca se filtra hacia arriba;
  • El sidecar usa la clave de caché ::local y corre bajo su propio harness daemon (BU_NAME=_local_<name>). El daemon lee su entorno CDP una vez al arrancar, así que compartir el daemon de la ruta de la nube retransmitiría la URL privada al navegador en la nube. Daemons separados, no uno compartido;
  • Una llamada posterior sin ningún literal de URL se mantiene en la ruta que tomó la llamada anterior.

El suelo de seguridad no se movió

Los fallbacks son un sitio fácil para colar un agujero, así que se mantuvieron todas las invariantes existentes:

  • Las direcciones de metadatos de la nube siguen bloqueadas incondicionalmente: 169.254.169.254 sigue respondiendo “URL targets a cloud metadata endpoint”, sea cual sea el proveedor;
  • browser.auto_local_for_private_urls está activado por defecto; ponerlo en false sigue rechazando las URL privadas, salvo que además actives browser.allow_private_urls, que deja al proveedor en la nube intentarlo — normalmente en vano, porque Browserbase y compañía no llegan a tu LAN;
  • Un override de CDP no se considera local: si defines browser.cdp_url o usas /browser connect, el enrutado híbrido devuelve falso y el comportamiento no cambia;
  • Una redirección de público a privado se rechaza, no se filtra;
  • La reverificación de la URL de destino tras la ejecución no se toca.

El fallback local necesita el sidecar local — el mismo CLI agent-browser que usa el modo puramente local. hermes setup tools → Browser Automation te lo instala.

Estado y actualización

Ambos arreglos (PRs #136447 y #136442) se fusionaron en main el 2026-10-11. El tag actual, v0.21.6 del 8 de octubre, no los incluye; la próxima release sí. Para usarlos hoy, actualiza desde main.

Tras actualizar, puedes confirmar que el fallback se activó buscando esa única línea de fallo del proveedor en el log — aparece una vez y luego la llamada del navegador continúa en el Chromium local.

Para terminar

Un navegador en la nube es rápido y cómodo, pero es un servicio externo: aplica rate limits y se cae. La configuración que de verdad aguanta es nube primero, local como suelo, y direcciones sensibles que nunca salen de la máquina. Ese suelo es lo que restauran estos cambios: la caída de un proveedor ya no termina tu tarea, los servidores de desarrollo y las páginas de la LAN se abren como antes, y ni las direcciones internas ni los endpoints de metadatos de la nube se acercaron a la nube.

Lecturas relacionadas: cómo las herramientas de navegador integradas pasaron a ser las predeterminadas explica la selección de proveedor, el presupuesto de snapshots del navegador aborda otro tipo de “el navegador va lento”, y los retos del navegador en el plan gratuito cubre los compromisos de usar un navegador en la nube con presupuesto ajustado.