Skip to content

[Bug]: TcpListener leak after Domain Reload regressed in 9.7.1 on Windows (stdio) — closed by PR #688 / issue #692, but listener-task wait reduced from 2000ms → 500ms and silent port-fallback masks the failure #1173

@awaldrop

Description

@awaldrop

What happened?

PR #688 (which closed #692) is structurally present in 9.7.1 - ExclusiveAddressUse=true on Windows, AssemblyReloadEvents.beforeAssemblyReload registered, listener.Server.Dispose() in Stop() - but the listener-task wait was reduced from PR #688's 2000ms back to 500ms, which appears too short on Windows for the socket to fully release. The new bridge then hits AddressAlreadyInUse, silently falls back to a new port via PortManager.DiscoverNewPort(), and the Python server keeps talking to the orphan on the original port. Symptoms match #692 exactly.

After the 2nd play-mode entry within a single Editor session (default Enter Play Mode Options, Reload Domain enabled), scene-graph tools - find_gameobjects, manage_scene get_hierarchy, execute_code — return {"success": false, "error": "busy", "data": {"reason": "compiling", "retry_after_ms": 500}} indefinitely or time out. read_console and manage_editor (play/stop) keep working - consistent with OS round-robin between the orphaned and live listeners.

Verified locally against the installed 9.7.1 package:

MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs:343 - ExclusiveAddressUse = true (matches PR #688)
MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs:33 - AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload (matches PR #688)
MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs:374 - listener?.Server?.Dispose() in Stop() (matches PR #688)
MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs:400 - toWait.Wait(500) (PR #688 used 2000ms)
MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs:73 - stopTask.Wait(500) (also benefits from a longer timeout)

Suggested action:

Restore the 2000ms toWait.Wait(...) at StdioBridgeHost.cs:400 from PR #688, and bump stopTask.Wait(500) at StdioBridgeReloadHandler.cs:73 correspondingly. The PR description explicitly called out the longer timeout as part of the Windows fix.

Reconsider the silent port-fallback at StdioBridgeHost.cs:285-305. Failing fast on the configured port would surface the orphan instead of masking it. If the fallback is kept, the Python server needs a discovery mechanism (status file, heartbeat) so it doesn't stay glued to the dead port.

Workaround until fixed: Project Settings > Editor > Enter Play Mode Settings > Reload Domain -> off (no reload -> no socket churn). Confirmed clean across 4+ play cycles in this configuration.

Reproduction steps

  1. Fresh Unity Editor session, scene loaded, default Enter Play Mode Options (Reload Domain enabled).
  2. Call manage_editor with action=play from the MCP client -> cycle 1 works, tools respond normally.
  3. Call manage_editor with action=stop.
  4. Call manage_editor with action=play again -> the bridge log now reports StdioBridgeHost started on port 6402 instead of the configured 6400.
  5. Call any scene-graph tool (find_gameobjects, manage_scene get_hierarchy, execute_code) -> returns {"success": false, "error": "busy", "data": {"reason": "compiling", "retry_after_ms": 500}} indefinitely, or times out, until play mode exits.

Unity version

6000.3.16f1 (Unity 6.3 LTS)

MCP for Unity package version

9.7.1

Python server version

9.7.1

MCP client

Claude Desktop

Transport

stdio

OS

Windows

Relevant logs / console output

Bridge startup after the 2nd play-mode entry (port silently shifted from 6400 -> 6402):

MCP-FOR-UNITY: StdioBridgeHost started on port 6402. (OS=WindowsEditor, server=9.7.1)
Game loaded successfully
Offline earnings: 11330.690890233 bones (14406.472842 seconds away)
Initial state: 138447.458279978 bones, 1.573 BPS, 4 BPT
Game saved successfully
Tool calls from the MCP client (configured port 6400) immediately after the port shift:

find_gameobjects(search_term="EventSystem", search_method="by_name")
  -> MCP error -32001: Request timed out
find_gameobjects(search_term="EventSystem", search_method="by_name")  [retry]
  -> {"success": false, "message": "compiling", "error": "busy",
     "data": {"reason": "compiling", "retry_after_ms": 500}, "hint": "retry"}
manage_scene(action="get_hierarchy", max_depth=2)
  -> {"success": false, "message": "compiling", "error": "busy",
     "data": {"reason": "compiling", "retry_after_ms": 500}}
On the play cycle where the orphan first appeared, the Unity console also logged:

MCP-FOR-UNITY: Client handler error: Cannot access a disposed object.
MCP-FOR-UNITY: Client handler error: Cannot access a disposed object.
MCP-FOR-UNITY: Client handler error: Cannot access a disposed object.
MCP-FOR-UNITY: Client handler error: Cannot access a disposed object.
Bisect — Reload Domain is the variable, Run In Background is not:

Reload Domain	Run In Background	Result
ON		OFF			bug reproduces on cycle 2
ON		ON			bug reproduces on cycle 2
OFF		OFF			4+ play cycles clean, port stays on 6400

manage_editor(action="stop")  -> success
find_gameobjects(search_term="EventSystem")  -> {"instanceIDs": [68324], "totalCount": 1}

Checks

  • I searched existing issues and did not find a duplicate
  • I included logs / steps to reproduce

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions