# Debug Session: tcl-async-delete-wrong-thread  [OPEN]
**Fecha:** 2026-09-20
**SessionId:** tcl-async-delete-wrong-thread
**Bug Reportado Alex:** `Tcl_AsyncDelete: async handler deleted by the wrong thread`
**Reproducción Manual:** `python.exe -B .\optimus_user_agent.py` en runtime_v1 PS. Proceso muere, GUI/tray desaparece.
**Goal Hotfix4:** Identificar ROOT CAUSE, aplicar FIX MÍNIMO, regresar python.exe + pythonw.exe + shutdowns limpios. Preservar HF2 + HF3.

---

## Hipótesis Falsables (4)

| # | Hipótesis | Mecanismo Esperado | Dónde Probar |
|---|---|---|---|
| H1 | **root.destroy() / Tk operations called FROM NON-MAIN THREAD** (worker, tray callback, atexit, signal handler). Shutdown anterior FreeConsole + `_shutdown_lock` NO cubría TODAS las rutas. | Thread != main → `root.after` no ejecutado realmente → destrucción directa Tk desde tray/worker. | TK_TRACE events SHUTDOWN_REQUESTED, TK_DESTROY_EXECUTED, TRAY_STOP_REQUESTED. Comparar THREAD_ID vs MAIN_THREAD_ID. |
| H2 | **atexit + GC __del__ de objetos Tk (PhotoImage, StringVar) corriendo en GC thread != main.** python.exe (con consola) timing diferente causa que `sys.exit` + `atexit` desregistren handlers en orden distinto al de pythonw.exe (sin consola). | Atexit cleanup tray → Python GC → `PhotoImage.__del__` o `StringVar.__del__` invocado en Python GC thread cuando main Tk ya no existe pero Tcl todavía tiene handlers. | EXCEPTION trace, TK_TRACE con `shutdown_phase=atexit`, THREAD_NAME != MainThread. |
| H3 | **pystray (Tray) callback (on_clicked, shutdown menu item) llama root.destroy() DIRECTAMENTE desde pystray internal thread (Tk main alternativo / pystray message pump)** que NO ES EL MISMO TK MAIN THREAD que creó el root. (Race: tray stop vs Tk destroy circular join). | Pystray tray_item menu click → callback thread → `self._shutdown_tray_and_ui()` → `root.destroy()` en thread de pystray (no main). | TRAY_STARTED thread_name vs TK_MAIN_THREAD; callback log caller. |
| H4 | **FreeConsole() timing + consola python.exe interactúa mal con Tcl.** Hotfix estabilidad anterior llamaba `FreeConsole()` para desligar grupo de consola (Tcl_AsyncDelete cuando PowerShell cierra). En python.exe modos: 1) sin consola después FreeConsole, 2) Tcl todavía tiene atexit handlers asociados consola que se disparan en exit y cruzan threads. | FreeConsole llamado MÁS TEMPRANO o MÁS TARDE que Tk init. Corroborar diferencia TCL count entre pythonw (sin console init) y python (console init → luego free). | TK_TRACE FREE_CONSOLE_CALLED antes/después de TK_ROOT_CREATE_BEGIN. |

---

## Lifecycle REAL a Reconstruir (§3)

```
PROCESS → MAIN THREAD → FreeConsole? → SINGLE_INSTANCE check → TK_ROOT create → GUI init
  → TRAY init (pystray thread?) → MAINLOOP_BEGIN
  → WORKERS (free-text, SD, tray pump)
  → SHUTDOWN_TRIGGER (GUI X / Tray menu / Kill Switch / WM_CLOSE)
  → TRAY STOP  ↔?↔  TK DESTROY   (race?)
  → atexit handlers (Tcl, Tk destroy, tray)
  → THREAD JOINs
  → PROCESS EXIT
```

---

## Evidencias Previas (de HF3, usado para priorizar H1/H2/H3)

1. Hotfix Estabilidad Runtime anterior implementó:
   - `FreeConsole()` en modo normal para desligar consola PowerShell (psh closed → Tcl crash).
   - `_shutdown_lock` + cleanup atexit handlers.
2. Alex reprodujo con **python.exe** (con consola, diferente timing que pythonw.exe).
3. TCL_ASYNC_DELETE_COUNT=0 en scheduled task NO implica PASS en python.exe.
4. §21 HOTFIX 4 prohíbe: sleep, try/except ignore Tcl, os._exit principal, kill threads fuerza.

---

## Inventario TK/TCL (§4 — output de Grep se pegará aquí)

(PENDIENTE HF4-01)

---

## Thread Map (§5 — output análisis estático → luego TK_TRACE real)

| Rol | Esperado Thread ID | Confirmado? |
|---|---|---|
| MAIN_THREAD_IDENTIFIED | (main) | |
| TK_ROOT_CREATE | | |
| MAINLOOP | | |
| TRAY_CREATE | | |
| SHUTDOWN_REQUESTED_CALLER | | |
| TK_DESTROY_EXECUTED_CALLER | | |

---

## Instrumentation (§6 — Events TK_TRACE)

(PENDIENTE HF4-03)

## Reproducción (§17 — Alex exact command)

(PENDIENTE HF4-04)

## ROOT_CAUSE (§19)

(PENDIENTE HF4-05)

## FIX (§20)

(PENDIENTE HF4-06)

## Tests Post-Fix (§11/13-16)

(PENDIENTE HF4-07)

## INFORME FINAL (§28)

(PENDIENTE HF4-08)
