CoplayDev/unity-mcp · warning · ConnectionError

Connection closed while reading

Error message

Connection closed while reading

What it means

Raised by read_exact() in stress_editor_state.py when asyncio.StreamReader.read() returns an empty bytes object before the requested byte count is satisfied. An empty read signals the remote end (the Unity MCP TCP bridge) closed the socket. This is a normal occurrence under stress — the bridge may drop idle or misbehaving connections, restart during domain reload, or hit a connection cap.

Source

Thrown at tools/stress_editor_state.py:57

    default_port = 6400
    files = find_status_files()
    for f in files:
        try:
            data = json.loads(f.read_text())
            port = int(data.get("unity_port", 0) or 0)
            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. This is expected behavior under stress — the stress_loop already catches ConnectionError and reconnects. Increase reconnect delay or reduce request rate if errors dominate.
  2. Ensure Unity is running and the bridge is active before starting: check ~/.unity-mcp/unity-mcp-status-*.json for a valid unity_port.
  3. If errors are constant (never a successful request), verify the port is correct and the bridge is listening.
  4. Avoid running during Unity recompilation, or expect reconnect bursts around domain reloads.
Defensive patterns

Strategy: retry

Validate before calling

import socket

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

# Before starting the stress loop:
if not bridge_reachable(host, port):
    raise SystemExit(f"Bridge not reachable at {host}:{port}")

Try / catch

# The stress_loop already does this:
try:
    response = await asyncio.wait_for(read_frame(reader), timeout=TIMEOUT)
except (ConnectionError, OSError, asyncio.TimeoutError) as e:
    stats["errors"] += 1
    stats["reconnects"] += 1
    writer = None  # force reconnect on next iteration
    await asyncio.sleep(0.5)

Prevention

When it happens

Trigger: The Unity Editor bridge closed the TCP connection mid-frame: the editor exited, recompiled (domain reload tears down the bridge), hit a max-connection limit, or the OS killed the socket. Also fires if the bridge was never fully started and accepts then immediately drops the connection.

Common situations: Running the stress test while Unity is recompiling scripts (domain reload restarts the bridge); the bridge has a connection ceiling and the stress tool exceeds it; the editor crashed or was closed mid-test; network/firewall interference on the loopback socket.

Understand the failure class

Related errors


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