Resumen
El watchdog (vigilante) que monitorea y recupera un auto-update fallido del Agente Local moría antes de escribir su primera línea de log, quedando el Agente completamente muerto si el antivirus bloqueaba el arranque de la versión recién descargada.
Ocurrencias:
- 03-07-2026 (mañana)
- 05-07-2026 (tarde) — detectado durante swap 2.13.1→2.13.2
Causa raíz: Uso de flag DETACHED_PROCESS en el Popen del script PowerShell del watchdog, que priva a la consola de Windows de acceso válido → PowerShell muere en la inicialización del host antes de ejecutar el script.
Estado: ✅ RESUELTO en Agent 2.13.3 (APP v1.45.9, commit be210c6).
Línea temporal
| Fecha | Evento | Nota |
|---|---|---|
| 2.11.4+ | Watchdog introducido (agent-robustez F2) | El vigilante debe relanzar la versión nueva o revertir la anterior si falla el arranque |
| 2.13.1 | Traza persistente en update_watchdog.log | Primera evidencia: “watchdog lanzado (pid XXXX)” pero .ps1 jamás escribía su log |
| 03-07-2026 | Primer incidente reportado | Agente muere tras actualización; recuperación manual |
| 05-07-2026 | Segundo incidente + diagnóstico | Swap 2.13.1→2.13.2: registro de lanzamiento del PID pero log vacío → pista decisiva |
| 05-07 12:56 | Causa raíz cazada y fix verificado | DETACHED_PROCESS + CREATE_NO_WINDOW son modos excluyentes → PowerShell sin handles válidos |
| 05-07 11:24 | Fix mergeado a main (2.13.3) | Nueva línea de flags: CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW + DEVNULL en 3 handles |
Descripción técnica
Síntoma observado
En update_watchdog.log (introducido en 2.13.1):
[2026-07-05 12:56:00] Swap a v2.13.2: watchdog lanzado (pid 20072, powershell)
[log vacío hasta el siguiente arranque del Agente]
El watchdog nunca ejecutó su primera línea de log, pese a tener un PID válido.
Diagnóstico reproducido en laboratorio
Código problemático (hasta 2.13.2):
proc = subprocess.Popen(
[ps_exe, "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File", str(ps1)],
creationflags=subprocess.DETACHED_PROCESS
| subprocess.CREATE_NEW_PROCESS_GROUP
| getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
El problema:
DETACHED_PROCESSanulaCREATE_NO_WINDOW(son modos de consola mutuamente excluyentes en CreateProcess de Windows).- Con una consola inválida y sin handles explícitos (
stdin/stdout/stderr), PowerShell muere en su host initialization (antes de cargar el script). - El proceso nace (obtiene PID) pero fallece en microsegundos → no hay tiempo para la primera línea del script.
Por qué es silencioso:
- El exit code del hijo nunca se recoge → no hay excepción en Python.
- El
.ps1nunca corre → no escribe su log. - El proceso padre (Agente en swap) hace
os._exit(0)sin esperar, dejando apowershell.exemoribundo.
Solución (Agent 2.13.3)
proc = subprocess.Popen(
[ps_exe, "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File", str(ps1)],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | getattr(subprocess, "CREATE_NO_WINDOW", 0),
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
)
Cambios:
- ❌ Eliminar
DETACHED_PROCESS(causaba conflicto conCREATE_NO_WINDOW). - ✅ Mantener
CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW(consola propia y oculta). - ✅ Asignar explícitamente
stdin/stdout/stderr=DEVNULL→ PowerShell obtiene handles válidos.
Comportamiento post-fix:
- PowerShell inicia correctamente, aunque el padre haga
os._exit(0)inmediatamente. - En Windows, los procesos hijo sobreviven al padre por defecto → el script del watchdog corre hasta completarse.
- El proceso se desacopla de verdad: el Agente muere, el vigilante monitorea y actúa (relanza o revierte).
Alcance y transición
⚠️ Actualización HACIA 2.13.3 (última con mecanismo viejo)
- El updater aún usa los flags antiguos.
- Si el AV bloquea el arranque, el watchdog falla silenciosamente.
- Workaround: relanzar el Agente a mano (
agent.exe).
✅ Actualización DESDE 2.13.3 en adelante
- El nuevo updater (en 2.13.3) lanza el watchdog con los flags corregidos.
- El vigilante corre de verdad: monitorea con
GET /health, relanza con--swap-retry, revierte si es necesario.
Verificación
Test de regresión (src inspection)
Archivo: tests/agent/test_agent_updater_trace.py (nueva función test_watchdog_launch_sin_detached_process):
def test_watchdog_launch_sin_detached_process():
"""Regresión del watchdog mudo (incidentes 03-07 y 05-07-2026)."""
import inspect
src = inspect.getsource(updater._launch_swap_watchdog)
assert "subprocess.DETACHED_PROCESS" not in src
assert "CREATE_NO_WINDOW" in src
assert "DEVNULL" in src
Motivo CI Linux: Los flags de Windows no existen en POSIX → test por inspección del source (verificar que el código no contiene DETACHED_PROCESS).
Impacto usuario
| Rol | Antes | Después |
|---|---|---|
| Admin de flota | Auto-update falla silenciosamente si el AV bloquea → hay que relanzar Agente a mano en cada máquina afectada. | Auto-update se recupera automáticamente; el vigilante relanza o revierte sin intervención. |
| Soporte | Incidentes de “Agente muerto después de actualizar” sin explicación clara en logs. | Diagnóstico claro en update_watchdog.log (lanzamiento, resultado del healthcheck, acción tomada). |
| Dev | Raíz oscura (flags de Windows contradictorios, timing race en PowerShell). | Causa documentada, fix verificado, test de regresión. |
Véase también
- [[feature—terminal—agent-robustez-f2-watchdog-recovery]]
- [[entity—terminal—service—updater-swap-watchdog]]
- [[decision—20260705—flags-createprocess-windows-watchdog]]
- [[concept—infra—auto-update-resilience]]
- [[runbook—terminal—diagnose-agent-update-failure]]
Referenciado desde
- Concepto: Resiliencia del auto-update silencioso (Agent Full-Local)
- Decisión: Flags de CreateProcess en Windows para el watchdog post-swap (DETACHED_PROCESS vs CREATE_NO_WINDOW)
- Fix: Vigilante del auto-update funciona por fin (Agent 2.13.3, causa raíz cazada)
- Runbook: Diagnosticar y recuperarse de fallos de auto-update del Agente
- Servicio: Watchdog de monitoreo post-swap del auto-update