Incidente: agent.log se quedaba mudo tras arrancar uvicorn (Local Agent, hasta v2.23.0)
Cuándo
Detectado y arreglado el 27-08-2026 (task #269, PR #444, commit a59375ab), publicado como Local Agent 2.23.1 (v1.85.7 del SaaS). El bug llevaba activo desde el rewrite del Agente a la versión 2.0 — es decir, meses de logs silenciosamente incompletos en toda la flota, no un caso aislado.
Síntomas visibles
El fichero agent.log que el Local Agent escribe en su carpeta de instalación solo contenía las dos líneas previas al arranque del servidor web interno (uvicorn) y nada más: ni los sondeos periódicos, ni la purga/VACUUM semanal de la base de datos SQLite local, ni los errores de la conexión WebSocket con el SaaS quedaban registrados en disco. Esa información solo vivía en el buffer de memoria de la Debug Console (los últimos 500 mensajes, ws://localhost:5050/ws/debug), que se perdía al cerrar la consola o reiniciar el Agente. Ningún dato de monitorización enviado al SaaS se vio afectado — el problema era puramente de trazabilidad para diagnóstico.
Explica retroactivamente por qué el “TimeoutError mudo” de la task #223 nunca dejó rastro en el log: el error ocurría después del arranque de uvicorn, momento en el que el fichero ya había dejado de escribir.
Causa raíz
terminal/agent/app_main.py arrancaba el servidor con uvicorn.run(app, host="127.0.0.1", port=PORT, log_level="error"). Sin log_config explícito, uvicorn aplica su LOGGING_CONFIG por defecto vía logging.config.dictConfig, que cierra todos los handlers del logger raíz existentes antes de reconfigurarlo. El Agente abre su FileHandler en modo "w" (sobrescribir en cada arranque); una vez que dictConfig lo cierra, Python no lo vuelve a abrir — es un comportamiento documentado de la librería estándar (CPython issue #42378), no un fallo de uvicorn. A partir de ahí, cada logger.info(...) posterior al uvicorn.run(...) se descartaba en silencio: sin excepción, sin warning.
Fix aplicado
Commit a59375ab (PR #444, task #269): una línea en terminal/agent/app_main.py — uvicorn.run(app, host="127.0.0.1", port=PORT, log_level="error", log_config=None). Con log_config=None, uvicorn no toca la configuración global de logging; el FileHandler del Agente sigue vivo, y log_level="error" se sigue aplicando igual a los loggers propios de uvicorn (uvicorn.error, uvicorn.access).
Cambios acompañantes:
terminal/agent/version.py:AGENT_VERSION2.23.0 → 2.23.1, con la entrada de changelog interno.tests/agent/test_agent_2231.py(nuevo): reproduce el mecanismo conlogging.config.dictConfigpuro de librería estándar (sin dependencia de uvicorn), y un segundo test conuvicorn.config.Config(...).configure_logging()que se salta (pytest.importorskip) en el contenedor de tests del SaaS, donde uvicorn no está instalado como dependencia.- Verificado además en el portátil de Edu:
agent.logcrece tras el arranque (según CHANGELOG.md/RELEASE_NOTES.md del commit). CHANGELOG.md/RELEASE_NOTES.md/README.md: entradas correspondientes a v1.85.7.
Lecciones
- Cualquier librería que reconfigure el logging global (
dictConfig,basicConfig, frameworks web con logging propio) es sospechosa de romper handlersFileHandler(mode="w")ya abiertos: el cierre es silencioso y no lanza excepción, así que el síntoma es “el log deja de crecer”, no un error visible. - Un log que deja de escribir no dispara ninguna alerta por sí solo — hicieron falta meses y una task de investigación (#223) para notar el patrón indirectamente, vía un error sin traza.
- La solución (
log_config=None) es de una sola línea, pero encontrar la causa raíz exigió conocer el mecanismo interno dedictConfigsobre handlers existentes — no es intuitivo desde el síntoma.
Preventivos futuros
- El test añadido (
tests/agent/test_agent_2231.py) fija el comportamiento esperado (log_config=Noneen la llamada auvicorn.run) para que una regresión futura falle en CI en vez de descubrirse meses después. - Límite honesto documentado en RELEASE_NOTES.md:
agent.logno rota — puede crecer varios MB con el Agente sondeando semanas seguidas; se vacía en cada arranque (comportamiento ya existente, sin cambios). La traza que sobrevive a reinicios sigue siendoupdate_watchdog.log, noagent.log.
Véase también
- [[concept—terminal—local-agent]]
- [[crearack-tech—admin—local-agent]]
- [[crearack-tech—backend—local-agent]]
- [[crearack—terminal—local-agent]]
- [[crearack-tech—guides—local-agent-guide]]
- [[entity—terminal—model—agentinstance]]
- [[feature—agent—resync-throttle-v2160]]