Problem
tests/unit/mcp/test_mcp_tools.py mocks subprocess.Popen but leaves _start_spawn_watchdog live. Four unit tests start real daemon threads for fake runner PIDs. Those threads outlive the test's temporary trace directory and monkeypatches. After the 60-second deadline, they can invoke real process-tree termination, cancel reservations, write into the restored trace directory, and dispatch desktop failure notifications for simulated tasks.
This was observed during an extended Windows deterministic-suite run: a desktop "Artemis Task Failed" notification referred to fake PID 54321 and test conversation conv-2. The notification's trace ID matched the combined test worktree's notification log. No private screenshot is attached.
Root cause
mobile_run_task() calls _start_spawn_watchdog() after the mocked Popen returns. temp_trace_env removes its directory and restores trace_store.TRACES_DIR before the real monitoring thread completes. Mocking only the child process does not isolate the background work that manages it.
Safe reproduction
A temporary pytest guard intercepted threading.Thread.start for names starting with spawn-watchdog-, recorded attempts without actually starting them, and asserted at teardown that no such attempts occurred. Other threads were left unchanged. Running the MCP tool module with that guard on 4f9eb3d produced:
ERROR test_mobile_run_task_reserves_and_passes_global_queue_ticket
ERROR test_mobile_run_task_with_device_serial
ERROR test_mobile_run_task_forwards_pro_tuning_to_background_runner
ERROR test_mobile_run_task_omits_pro_tuning_flags_when_unset
AssertionError: Test leaked 1 real spawn watchdog thread(s)
25 passed, 4 errors in 1.55s
The guard prevents process termination and notifications while reproducing the leak.
Fix and after log
An autouse fixture local to the MCP test module now replaces the watchdog launcher for mocked MCP dispatch tests. Queue/serial dispatch tests assert the exact watchdog arguments. Production watchdog behavior remains covered separately by test_spawn_watchdog.py, including successful startup, terminal tasks, deadlines, cleanup, and notifications with their external effects mocked.
Same guarded MCP tool module: 25 passed in 1.23s
Dedicated production watchdog tests: 4 passed in 0.49s
The fixture is scoped to each test in the MCP tool test module (not to the entire suite). This is an additional test-isolation fix in #42; no production behavior changes are needed.
Problem
tests/unit/mcp/test_mcp_tools.pymockssubprocess.Popenbut leaves_start_spawn_watchdoglive. Four unit tests start real daemon threads for fake runner PIDs. Those threads outlive the test's temporary trace directory and monkeypatches. After the 60-second deadline, they can invoke real process-tree termination, cancel reservations, write into the restored trace directory, and dispatch desktop failure notifications for simulated tasks.This was observed during an extended Windows deterministic-suite run: a desktop "Artemis Task Failed" notification referred to fake PID
54321and test conversationconv-2. The notification's trace ID matched the combined test worktree's notification log. No private screenshot is attached.Root cause
mobile_run_task()calls_start_spawn_watchdog()after the mockedPopenreturns.temp_trace_envremoves its directory and restorestrace_store.TRACES_DIRbefore the real monitoring thread completes. Mocking only the child process does not isolate the background work that manages it.Safe reproduction
A temporary pytest guard intercepted
threading.Thread.startfor names starting withspawn-watchdog-, recorded attempts without actually starting them, and asserted at teardown that no such attempts occurred. Other threads were left unchanged. Running the MCP tool module with that guard on4f9eb3dproduced:The guard prevents process termination and notifications while reproducing the leak.
Fix and after log
An autouse fixture local to the MCP test module now replaces the watchdog launcher for mocked MCP dispatch tests. Queue/serial dispatch tests assert the exact watchdog arguments. Production watchdog behavior remains covered separately by
test_spawn_watchdog.py, including successful startup, terminal tasks, deadlines, cleanup, and notifications with their external effects mocked.The fixture is scoped to each test in the MCP tool test module (not to the entire suite). This is an additional test-isolation fix in #42; no production behavior changes are needed.