CoplayDev/unity-mcp · warning · ConnectionError

Connection closed while reading

Error message

Connection closed while reading

What it means

Raised by read_exact() in stress_mcp.py when asyncio.StreamReader.read() returns empty bytes before the full requested length is received, indicating the Unity MCP bridge closed the TCP connection. Under the multi-client stress test this is a routine event — connections are churned, the bridge restarts on domain reload, and the client_loop catches this and reconnects with backoff.

Source

Thrown at tools/stress_mcp.py:58

            if project_path:
                # Match status for the given project if possible
                if proj and project_path in proj:
                    if 0 < port < 65536:
                        return port
            else:
                if 0 < port < 65536:
                    return port
        except Exception:
            pass
    return default_port


async def read_exact(reader: asyncio.StreamReader, n: int) -> bytes:
    buf = b""
    while len(buf) < n:
        chunk = await reader.read(n - len(buf))
        if not chunk:
            raise ConnectionError("Connection closed while reading")
        buf += chunk
    return buf


async def read_frame(reader: asyncio.StreamReader) -> bytes:
    header = await read_exact(reader, 8)
    (length,) = struct.unpack(">Q", header)
    if length <= 0 or length > (64 * 1024 * 1024):
        raise ValueError(f"Invalid frame length: {length}")
    return await read_exact(reader, length)


async def write_frame(writer: asyncio.StreamWriter, payload: bytes) -> None:
    header = struct.pack(">Q", len(payload))
    writer.write(header)
    writer.write(payload)
    await asyncio.wait_for(writer.drain(), timeout=TIMEOUT)

View on GitHub (pinned to c21bf496bc)

Solutions

  1. Expected under stress — client_loop catches ConnectionError/OSError and reconnects with exponential backoff. Monitor 'disconnects' in the stats output.
  2. Reduce --clients if disconnects dominate successful pings, indicating the bridge's connection ceiling is the bottleneck.
  3. Ensure Unity is open and the bridge is active before launching; check unity_port in the status JSON.
  4. Pause the reload_churn_task or reduce storm-count if domain reloads are tearing down the bridge too frequently.
Defensive patterns

Strategy: retry

Validate before calling

import socket

def bridge_listening(host: str, port: int) -> bool:
    try:
        with socket.create_connection((host, port), timeout=2.0):
            return True
    except OSError:
        return False

Try / catch

# client_loop already handles this:
except (ConnectionError, OSError, asyncio.IncompleteReadError, asyncio.TimeoutError):
    stats["disconnects"] += 1
    await asyncio.sleep(reconnect_delay)
    reconnect_delay = min(reconnect_delay * 1.5, 2.0)
    continue

Prevention

When it happens

Trigger: The bridge dropped the connection during a ping/read cycle: editor recompilation triggered a domain reload (bridge restart), the number of concurrent clients exceeded the bridge's connection cap, or the editor was closed/crashed. Also fires when connecting to a port where nothing is listening and the OS resets immediately.

Common situations: Running stress_mcp.py with high --clients during Unity recompilation; the bridge's max-connection limit is lower than the client count; editor closed mid-run; loopback socket reset by the OS.

Understand the failure class

Related errors


AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13). Data as JSON: /api/errors/36455c9335d979fb. Report an issue: GitHub.