Resumen
Módulo: terminal/agent/core/updater.py
Función pública: _launch_swap_watchdog(target: str) -> None
Versión agente: 2.13.3+ (funcional); 2.11.4–2.13.2 (presente pero inoperante hasta la corrección)
El watchdog es un proceso hijo desacoplado que monitorea la integridad de un auto-update del Agente Local post-swap. Se lanza cuando el updater reemplaza el binario del Agente y está a punto de hacer os._exit(0) para ceder control a la nueva versión.
Ciclo de vida:
- Lanzamiento: El updater ejecuta
_launch_swap_watchdog(target_version)con flags que permiten sobrevivir al padre. - Monitoreo: El watchdog espera 30 segundos (DEFAULT_SWAP_WATCHDOG_DELAY) y realiza healthcheck (
GET /healthen localhost:9999). - Acción correctiva:
- ✅ Healthcheck OK → Versión nueva viva, misión cumplida.
- ❌ Healthcheck falla → Versión nueva muerta (AV bloqueó arranque, corrupción, etc.).
- Intenta relanzar:
os.execv(new_exe, [new_exe, '--swap-retry', ...]) - Si sigue fallando → revierte al binario anterior.
- Intenta relanzar:
Firma de la función
def _launch_swap_watchdog(target: str) -> None:
"""Lanza un script PowerShell desacoplado que monitorea el swap post-actualización.
Args:
target: Versión destino del Agente (ej. "2.13.3"), usada en traza y naming.
Comportamiento:
- Genera un script .ps1 temporal con la lógica de healthcheck.
- Lo ejecuta con subprocess.Popen() con flags que permiten sobrevivir al padre.
- Retorna inmediatamente (el proceso hijo corre de fondo).
- Registra el PID en update_watchdog.log para auditoría.
Excepciones:
- FileNotFoundError: Si powershell.exe no está disponible.
- OSError: Error al escribir el script .ps1 o al ejecutar Popen.
"""
Componentes internos
1. Generación del script PowerShell (ps1)
Ubicación: Archivo temporal bajo AGENT_LOG_DIR (típicamente C:\Users\<user>\AppData\Roaming\CreaRack Pro\agent\).
Contenido del script:
- Escritura de log de arranque:
"[{timestamp}] Watchdog iniciado, versión destino: {target}" - Espera (delay):
Start-Sleep -Seconds $delay(default 30 segundos). - Healthcheck:
$response = Invoke-WebRequest -Uri "http://localhost:9999/health" -TimeoutSec 5 -ErrorAction Stop - Lógica de acción correctiva:
- Si healthcheck OK → log de éxito y salida.
- Si falla → log de error y reintentos (ejecutar
agent.exe --swap-retry).
Por qué PowerShell:
- Disponible por defecto en Windows moderno (>= Vista).
- Soporte nativo de HTTP (cmdlet
Invoke-WebRequest). - Decoupling del Agente Python (proceso independiente, lenguaje diferente).
2. Lanzamiento con subprocess.Popen()
Flags de creación (a partir de 2.13.3):
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | getattr(subprocess, "CREATE_NO_WINDOW", 0),
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
Por qué estos flags:
CREATE_NEW_PROCESS_GROUP: Crea un grupo de proceso independiente, desacoplado del padre.CREATE_NO_WINDOW: Abre una consola privada pero la oculta (no molesta al usuario).DEVNULLen los 3 handles: PowerShell obtiene I/O válidos; no muere al inicializar.close_fds=True: Cierra file descriptors heredados (mejor aislamiento).
Sin DETACHED_PROCESS: Aunque parezca contradictorio, no es necesario; CREATE_NEW_PROCESS_GROUP ya desacopla. Usarlo causaba conflicto semántico y mataba el proceso silenciosamente (incidentes 03-07 y 05-07).
3. Traza en update_watchdog.log
Ubicación: AGENT_LOG_DIR / "update_watchdog.log"
Registros que deja el updater (antes de lanzar):
[2026-07-05 12:56:00] Swap a v2.13.3: watchdog lanzado (pid 5432, powershell)
Registros que escribe el watchdog (desde el script .ps1):
[2026-07-05 12:56:00] Watchdog iniciado, versión destino: 2.13.3
[2026-07-05 12:56:30] Healthcheck OK, Agente v2.13.3 en ejecución
[2026-07-05 12:56:30] Misión cumplida, saliendo.
O en caso de fallo:
[2026-07-05 12:56:00] Watchdog iniciado, versión destino: 2.13.3
[2026-07-05 12:56:30] Healthcheck FALLÓ (AV bloqueó el arranque?), reintentando...
[2026-07-05 12:56:35] Reintento 1: ejecutando agent.exe --swap-retry
[2026-07-05 12:56:45] Healthcheck OK, Agente v2.13.3 vivo
O revertiendo:
[2026-07-05 12:56:00] Watchdog iniciado, versión destino: 2.13.3
[2026-07-05 12:56:30] Healthcheck FALLÓ
[2026-07-05 12:56:35] Reintento 1: FALLÓ
[2026-07-05 12:56:41] Reintento 2: FALLÓ (archivos corruptos?), revirtiendo a v2.13.2
[2026-07-05 12:56:45] Swap revertido, Agente v2.13.2 relanzado
Dependencias
| Dependencia | Fuente | Uso |
|---|---|---|
subprocess (stdlib) | Python builtin | Lanzamiento de PowerShell |
pathlib.Path (stdlib) | Python builtin | Ubicación de script .ps1, binary, etc. |
logging (stdlib) | Python builtin | Escritura en update_watchdog.log |
| PowerShell.exe | Windows system (C:\Windows\System32) | Intérprete del script |
| localhost:9999/health | Agent HTTP API | Healthcheck (endpoint de monitoreo) |
Historial de cambios
| Versión | Estado | Nota |
|---|---|---|
| 2.11.4 | ✅ Introducción | Primer watchdog (agent-robustez F2). |
| 2.11.5–2.13.2 | ❌ Inoperante | Flags incorrectos (DETACHED_PROCESS) mataban el script silenciosamente. |
| 2.13.1 | 📊 Traza añadida | Se introdujo update_watchdog.log (evidencia del problema). |
| 2.13.3 | ✅ Corregido | Eliminado DETACHED_PROCESS, añadido DEVNULL en handles. |
Casos de uso
Caso 1: AV bloquea el arranque de la versión nueva
- Updater lanza swap (reemplaza binary, hace
os._exit(0)). - Watchdog sobrevive, espera 30s.
- Versión nueva (2.13.3) arranca pero AV mata
agent.exeal extraer el onefile (sin reputación). - Healthcheck falla → watchdog ejecuta
agent.exe --swap-retry. - AV deja pasar el reintento (historial) → Agente vivo.
Caso 2: Binario corrupto
- Updater descarga versión nueva, swap aplicado.
- Versión nueva arranca pero falla al inicializar módulos (import error).
- Healthcheck falla repetidamente.
- Tras N reintentos → watchdog revierte al binario anterior.
- Usuario ve: Agente sigue vivo en v2.13.2 (sin experiencia degradada).
Caso 3: Actualización exitosa
- Updater lanza swap correctamente.
- Versión nueva (2.13.3) arranca sin problemas.
- Healthcheck OK a los 30s.
- Watchdog registra éxito y termina.
Restricciones y limitaciones
Windows-only
- Usa flags específicos de Windows (
CREATE_NEW_PROCESS_GROUP,CREATE_NO_WINDOW). - El script está en PowerShell (no disponible en Linux/Mac del Agente, pero el Agente is Windows-only by design).
No hay timeout global en Python
- Una vez lanzado, el watchdog no se monitorea desde el proceso padre (ya murió).
- Si el watchdog se cuelga indefinidamente, no hay mecánica de supervisión externa (el proceso simplemente permanece vivo).
- Mitigación: El script tiene timeouts locales en cada
Invoke-WebRequest(5 segundos).
Caso de fallo silencioso (hasta 2.13.2)
- Si el lanzamiento mismo falla (p.ej. PowerShell no disponible), la excepción en Python se ignora (
try/except OSError). - El Agente sigue adelante con
os._exit(0), dejando la versión nueva posiblemente muerta sin vigilancia.
Véase también
- [[incident—20260705—watchdog-auto-update-mudo]]
- [[feature—terminal—watchdog-recovery-fix-v2-13-3]]
- [[decision—20260705—flags-createprocess-windows-watchdog]]
- [[concept—infra—auto-update-resilience]]
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)
- Dos bugs críticos del build 2.14.0 en bootstrap e updater (07-07-2026)
- Fix: Vigilante del auto-update funciona por fin (Agent 2.13.3, causa raíz cazada)
- Incidente: Vigilante de auto-update muere silenciosamente post-swap (Agent 2.13.2, 03-07 y 05-07)
- Runbook: Diagnosticar y recuperarse de fallos de auto-update del Agente