CoplayDev/unity-mcp · error · ConnectionError
Connection closed before reading expected bytes
Error message
Connection closed before reading expected bytes
What it means
Raised by _read_exact (unity_connection.py:168) in the framed receive path. It loops calling sock.recv() to assemble a fixed byte count (the 8-byte length header or a payload); an empty recv() means TCP EOF before the expected bytes arrived, so the peer closed mid-message.
Source
Thrown at Server/src/transport/legacy/unity_connection.py:168
except BlockingIOError:
pass # No data pending; socket is alive
except Exception:
logger.debug("Stale socket detected; will reconnect on next send")
try:
self.sock.close()
except Exception:
pass
self.sock = None
finally:
if self.sock and orig_blocking is not None:
self.sock.setblocking(orig_blocking)
def _read_exact(self, sock: socket.socket, count: int) -> bytes:
data = bytearray()
while len(data) < count:
chunk = sock.recv(count - len(data))
if not chunk:
raise ConnectionError(
"Connection closed before reading expected bytes")
data.extend(chunk)
return bytes(data)
def receive_full_response(self, sock, buffer_size=config.buffer_size) -> bytes:
"""Receive a complete response from Unity, handling chunked data."""
if self.use_framing:
# Heartbeat semantics: the Unity editor emits zero-length frames while
# a long-running command is still executing. We tolerate a bounded
# number of these frames (or a small time window) before surfacing a
# timeout to the caller so tools can retry or fail gracefully.
heartbeat_limit = getattr(config, 'max_heartbeat_frames', 16)
heartbeat_window = getattr(config, 'heartbeat_timeout', 2.0)
heartbeat_started = time.monotonic()
heartbeat_count = 0
try:
while True:
header = self._read_exact(sock, 8)View on GitHub (pinned to c21bf496bc)
Solutions
- Let send_command's retry loop handle it — it catches the failure, drops the dead socket, and reconnects on the next attempt.
- Keep Unity open and avoid forcing recompiles during long tool calls.
- If it recurs constantly, inspect the Unity Editor console for crashes or repeated domain reloads.
Example fix
// before resp = conn.send_command(cmd, params) # raises on EOF // after — rely on retry; only surface to the user after retries are exhausted resp = conn.send_command(cmd, params, max_attempts=config.max_retries)
Defensive patterns
Strategy: retry
Validate before calling
# Validate the socket is live before sending
import select
def socket_alive(sock) -> bool:
if sock is None:
return False
r, _, x = select.select([sock], [], [sock], 0)
if x or (r and not sock.recv(1, socket.MSG_PEEK)):
return False # EOF or error pending
return True Type guard
def is_mid_stream_eof(e: BaseException) -> bool:
return (isinstance(e, ConnectionError)
and 'Connection closed before reading expected bytes' in str(e)) Try / catch
# Rely on send_command's built-in retry; it drops the dead socket and reconnects.
try:
resp = conn.send_command(cmd, params, max_attempts=config.max_retries)
except ConnectionError as e:
if 'Connection closed before reading expected bytes' in str(e):
conn.disconnect()
resp = conn.send_command(cmd, params) # one manual reconnect+retry Prevention
- Avoid forcing Unity recompiles while long tool calls are in flight.
- Keep Unity open for the duration of a command batch.
- Use the framed (default) transport, which is more robust to drops.
When it happens
Trigger: receive_full_response in framing mode reading the 8-byte header or the payload when Unity closes the socket — e.g. a Unity domain reload, an editor crash, or the user quitting Unity while a command is in flight.
Common situations: Unity recompiling scripts (domain reload invalidates the socket), the editor being closed mid-operation, or a network device killing the connection during a large framed response.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Connection closed before receiving data
- Timeout receiving Unity response
- Cannot connect to Unity MCP server at {cfg.host}:{cfg.port}.
- Unity sent heartbeat frames without payload within configure
- Invalid framed length: {payload_len}
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/4c266954eee3b46a.
Report an issue: GitHub.