Contexto
Reinstalar el .exe del Local Agent de forma manual (descarga + doble click) sobre una instancia viva dejaba instancias duplicadas corriendo (footgun s183). Esto ocurría porque:
-
psutil no iba empaquetado: la función
find_running_agent_processes()haceimport psutilpero la librería nunca entró en el onefile (nirequirements-agent.lockni--hidden-import), por lo que devolvía[]siempre. El kill fallaba silenciosamente en el binario (aunque funcionaba en desarrollo). -
La vía de respaldo (wmic) era insegura: cuando psutil fallaba, se usaba
wmic process ... get processid | taskkill /PID, pero mataba “todo excepto el pid actual”. En un onefile de PyInstaller el programa es en realidad DOS procesos (bootloader + hijo), así que podía matar a su propio padre o dejar duplicados. Además,wmicestá retirado en Windows 11 24H2+. -
Filtro por nombre era débil: un instalador descargado a
Downloads/CreaRackAgent.exey otro instalado en%APPDATA%\CreaRackAgent\tienen el mismo nombre. Matar por nombre (IMAGENAME) no distinguía.
Decisión
Matar instancias por PATH del ejecutable, no por nombre. Psutil viaja empaquetado en el .exe desde la versión 2.13.2 (Agent):
F1 · Kill seguro por PATH
-
Función principal:
find_running_agent_processes()filtra por ruta del ejecutable bajoINSTALL_DIR(excluyendo el par de procesos propio: pid + ppid).- Solo mata procesos cuyo
.exeestá en%APPDATA%\CreaRackAgent\(o la ruta configurada). - Nunca toca el instalador que corre desde Descargas (mismo nombre, distinto path).
- Excluye el par pid+ppid para que el onefile (bootloader + hijo) no se autolesione.
- Solo mata procesos cuyo
-
Implementación:
def _is_under_install_dir(exe_path: str) -> bool: """True si exe_path está DENTRO del directorio de instalación (case-insensitive).""" exe = Path(exe_path).resolve() install = INSTALL_DIR.resolve() return install == exe.parent or install in exe.parents def find_running_agent_processes() -> list: """Instancias del Agente corriendo desde el directorio de INSTALACIÓN.""" own_pids = {os.getpid(), os.getppid()} processes = [] for proc in psutil.process_iter(["pid", "name", "exe"]): if proc.info["pid"] not in own_pids and _is_under_install_dir(proc.info.get("exe") or ""): processes.append(proc) return processes -
Último recurso sin psutil (entornos dev):
taskkill /F /FI "IMAGENAME eq CreaRackAgent.exe" /FI "PID ne <pid>" /FI "PID ne <ppid>"Sin psutil disponible, se usan filtros
/FIde taskkill para excluir explícitamente el par de procesos propio. Se retirawmic(obsoleto, inseguro). -
Dependencia nueva: psutil 7.2.2 (BSD-3-Clause) → entra en
requirements-agent.lock+--hidden-import=psutilenbuild_agent.bat+ entrada enLICENSES_AGENT.txt.
F4 · Auditoría de empaquetado (gate de selftest)
El --selftest es el gate que protege cada build y cada swap de auto-update. Se amplía para que importe también los terceros lazy que aparecen en runtime:
psutil— usado por el installer al kill.pyasn1.codec.ber— decode de traps SNMP.pysnmp.proto.api— receiver de traps.
Si en una futura compilación se olvida alguno de estos hidden-imports, el selftest lo detecta ANTES de llegar a la flota. Corrige además un drift en ATTRIBUTIONS: aiohttp estaba listado como 3.13.4 cuando el lock empaqueta 3.14.0.
Nota: win10toast NO se empaqueta a propósito — su excepción ImportError tiene fallback a MessageBox nativo.
Cierre de items
- F1 (Agent 2.13.2): ✅ Cerrado — kill seguro por path, excluyendo pid+ppid propio.
- F4 (Agent 2.13.2): ✅ Cerrado — selftest importa terceros lazy; auditoría de hidden-imports completada.
El item F2 ya fue cerrado en Agent 2.13.1 (traza persistente del auto-update en update_watchdog.log). El item F3 queda pendiente (firma de la versión ≠ decisión técnica pendiente).
Impacto
- Usuario final: ninguno visible con esta versión. El beneficio se nota en la próxima reinstalación manual (descarga + doble click). No requiere instalación fresca — el fix viaja por auto-update.
- Desarrollador: psutil ahora es dependencia explícita del Agente (lock + hidden-import auditable).
- Build: exige recompilar el
.exe(2.13.2). La nueva cadena de build generaLICENSES_AGENT.txtactualizado con BSD-3-Clause de psutil.
Cobertura de tests
6 tests nuevos en tests/agent/test_agent_installer_kill.py:
- Filtro por path (dentro/fuera de INSTALL_DIR).
- Exclusión del par pid+ppid propio.
- Kill + espera (psutil.wait_procs).
- Degradación sin psutil (taskkill con filtros PID ne).
- Case-insensitivity en Windows.
Véase también
- [[entity—terminal—service—agent-installer]]
- [[entity—terminal—service—agent-selftest]]
- [[concept—terminal—local-agent-architecture]]
- [[feature—terminal—agent-auto-update]]
- [[incident—20260420—agent-duplicados-s183]]