Decisión: Flags de CreateProcess en Windows para el watchdog post-swap (DETACHED_PROCESS vs CREATE_NO_WINDOW)
Contexto
El watchdog (proceso hijo desacoplado) que monitorea el auto-update del Agente debe:
- Lanzarse desde el Agente que está a punto de reemplazarse.
- Sobrevivir al padre (que hace
os._exit(0)inmediatamente). - Ejecutar un script PowerShell que escribe logs y realiza healthchecks (
GET /health).
En Windows, subprocess.Popen(creationflags=...) requiere una combinación de flags que garantice:
- Que el proceso hijo no hereda la consola del padre (debe estar desacoplado).
- Que el child obtiene handles válidos para
stdin/stdout/stderr(PowerShell necesita escribir).
Alternativas consideradas
❌ Opción 1: DETACHED_PROCESS | CREATE_NO_WINDOW (hasta 2.13.2)
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW
Razonamiento original:
DETACHED_PROCESSgarantiza desacoplamiento del padre.CREATE_NEW_PROCESS_GROUPcrea un grupo de proceso nuevo (no asociado a la sesión del padre).CREATE_NO_WINDOWoculta la ventana de consola.
Problema descubierto (incidentes 03-07 y 05-07-2026):
DETACHED_PROCESSyCREATE_NO_WINDOWson modos mutuamente excluyentes enCreateProcessW.- Si se especifican ambos, Windows prioriza
DETACHED_PROCESSe ignoraCREATE_NO_WINDOW. - El proceso se crea sin consola válida y sin handles de entrada/salida.
- PowerShell.exe muere en su host initialization (antes de cargar el script) al intentar validar la consola.
- Síntoma: PID registrado en logs, pero el script jamás escribe su primera línea.
✅ Opción 2: CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW + stdin/stdout/stderr=DEVNULL (2.13.3+)
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
Razonamiento revisado:
CREATE_NEW_PROCESS_GROUP(sinDETACHED_PROCESS) crea un grupo nuevo y desacopla del padre correctamente.CREATE_NO_WINDOWabre una consola privada invisible (válida internamente).- Proporcionar handles explícitos (
DEVNULL) garantiza que PowerShell obtiene I/O válidos.
Ventajas:
- ✅ PowerShell inicializa correctamente.
- ✅ Los child processes sobreviven al padre en Windows (comportamiento por defecto).
- ✅ El script se ejecuta completamente, incluso si el padre hace
os._exit(0)al instante. - ✅ Verificado empíricamente en laboratorio (reproducción de ambos escenarios).
Por qué no usar explícitamente DETACHED_PROCESS:
- No es necesario:
CREATE_NEW_PROCESS_GROUPsin padre ya desacopla. - Causa conflicto semántico con
CREATE_NO_WINDOW. - Históricamente,
DETACHED_PROCESSse usaba en aplicaciones de servicio Windows (no guarded); aquí no aplica.
Decisión
Adoptar Opción 2: Reemplazar DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW por CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW + handles explícitos en DEVNULL.
Reasoning
- Correctness: Elimina el conflicto de flags que mataba silenciosamente el watchdog.
- Simplicity: Menos flags = menos superficies de ambigüedad en Windows API.
- Empirical verification: Probado en laboratorio; el nuevo setup permite que el script corra completamente.
- Backwards compatibility: No afecta el resto del código (solo el lanzamiento del watchdog).
Implementation
- Archivo:
terminal/agent/core/updater.py, función_launch_swap_watchdog(), líneas ~249–269. - Cambio: 4 líneas de
creationflagsreemplazadas por 4 líneas + 3 parámetros de handles. - Test de regresión:
tests/agent/test_agent_updater_trace.py::test_watchdog_launch_sin_detached_processverifica ausencia deDETACHED_PROCESSen el source.
Impacto
En el Agent
- Versión afectada: 2.13.3+ (el updater que lanza el watchdog con flags nuevos).
- Alcance: Solo la función
_launch_swap_watchdog()(privada, interna). - Efectos secundarios: Ninguno; el resto de los subprocesos del Agente no cambian.
En la flota
- Desde 2.13.3 en adelante, el watchdog funciona de verdad.
- Swaps previos (hasta 2.13.2) siguen siendo vulnerables a fallos silenciosos si el AV bloquea el arranque.
- Mitigación: El cambio es prospectivo; el primer swap a 2.13.3 puede fallar silenciosamente (watchdog viejo), pero todos los posteriores usan el vigilante corregido.
Véase también
- [[incident—20260705—watchdog-auto-update-mudo]]
- [[feature—terminal—watchdog-recovery-fix-v2-13-3]]
- [[entity—terminal—service—updater-swap-watchdog]]
- [[concept—infra—auto-update-resilience]]