CreaRack-SL

Incidente: Vigilante de auto-update muere silenciosamente post-swap (Agent 2.13.2, 03-07 y 05-07)

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

FechaEventoNota
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.1Traza persistente en update_watchdog.logPrimera evidencia: “watchdog lanzado (pid XXXX)” pero .ps1 jamás escribía su log
03-07-2026Primer incidente reportadoAgente muere tras actualización; recuperación manual
05-07-2026Segundo incidente + diagnósticoSwap 2.13.1→2.13.2: registro de lanzamiento del PID pero log vacío → pista decisiva
05-07 12:56Causa raíz cazada y fix verificadoDETACHED_PROCESS + CREATE_NO_WINDOW son modos excluyentes → PowerShell sin handles válidos
05-07 11:24Fix 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_PROCESS anula CREATE_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 .ps1 nunca corre → no escribe su log.
  • El proceso padre (Agente en swap) hace os._exit(0) sin esperar, dejando a powershell.exe moribundo.

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:

  1. ❌ Eliminar DETACHED_PROCESS (causaba conflicto con CREATE_NO_WINDOW).
  2. ✅ Mantener CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW (consola propia y oculta).
  3. ✅ 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

RolAntesDespués
Admin de flotaAuto-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.
SoporteIncidentes 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).
DevRaí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]]