GUI instance discovery file deleted moments after registration, even though instance is alive and reachable

Author: vuhzCreated Jul 28, 2026Updated Jul 28, 2026

Summary

discover_instances() in src/ida_pro_mcp/ida_mcp/discovery.py deletes a GUI instance's instance_<port>.json registration file almost immediately after it's written — even though the instance is fully alive and reachable at the time. This breaks idb_open(mode="prefer_gui"|"force_gui") and idb_list() for adopting an already-running GUI session via idalib-mcp.

Environment

  • OS: Windows 11
  • IDA Pro 9.3
  • Installed via the Claude Code plugin marketplace (claude plugin install ida-pro-mcp@mrexodia), invoked as uv run --project <plugin-cache> idalib-mcp --stdio
  • idalib activated via py-activate-idalib.py against the IDA 9.3 install

Repro

  1. Load the GUI plugin in IDA (ida_mcp.py, from this repo's ida-plugin.json entry point). Confirms in the IDA log:
    [MCP] Registered instance: ZDefend (pid=25252, port=13337)
      Discovery file: ...\Hex-Rays\IDA Pro\mcp\instances\instance_13337.json
  2. Immediately (within a few seconds) call idb_list via idalib-mcp --stdio:
    json
    {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"idb_list","arguments":{}}}
    Result: {"sessions":[],"count":0}
  3. Check the instances directory — the file is gone:
    ls "$APPDATA/Hex-Rays/IDA Pro/mcp/instances/"
    # empty
  4. At the same moment, the instance is still perfectly reachable:
    • curl -X POST http://127.0.0.1:13337/mcp ... → valid JSON-RPC response
    • Calling server_health directly against that port (via the non-idalib ida-pro-mcp stdio proxy, which doesn't go through this discovery/prune logic) returns real data (module, idb_path, hexrays_ready: true, etc.)

Reproduced this identically twice in one session (once against the original instance, once after re-triggering registration via Ctrl+Alt+M).

What I ruled out

Directly isolated both liveness checks used by discover_instances() against the live PID/port right after a fresh registration, and both passed on their own:

python
import discovery
discovery.is_pid_alive(25252)                       # -> True
discovery.probe_instance("127.0.0.1", 13337, 2.0)    # -> True

So it's not a static/permission issue with OpenProcess (same user, non-elevated) or a fundamentally wrong port. This points to a timing/race condition: the plugin's HTTP server (http.server-based, effectively single-threaded) may be momentarily busy servicing another request when discover_instances()'s probe runs, causing a transient connect failure that gets treated as "instance is dead" and triggers os.unlink(file_path) — permanently, since there's no periodic re-registration to recover from a false-negative prune.

Impact

  • idb_open(..., mode="prefer_gui") can't find/adopt the already-open GUI session, silently falls back toward a headless open of the same .i64 file, and fails with "Failed to open database" since the GUI process already holds an exclusive lock on it.
  • idb_list() undercounts/misses live GUI sessions entirely once the race hits once.
  • Headless-only multi-session (idb_open(mode="force_headless") for independent binaries) is unaffected and works correctly — verified opening two separate binaries concurrently, each with distinct session IDs, both listed correctly by idb_list.

Suggested directions

  • Don't delete the registration file on a single failed probe — require N consecutive failures (or a debounce window) before pruning, since is_pid_alive + probe_instance are both point-in-time checks against a possibly-momentarily-busy single-threaded server.
  • Alternatively/additionally, have the GUI plugin periodically refresh (touch/rewrite) its registration file so a wrongly-pruned entry gets restored on the next heartbeat instead of staying gone until the plugin is reloaded.