{"record":{"id":"ece5821b460c68da","repo":"CoplayDev/unity-mcp","slug":"invalid-frame-length-length-ece582","errorCode":null,"errorMessage":"Invalid frame length: {length}","messagePattern":"Invalid frame length: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/stress_mcp.py","lineNumber":67,"sourceCode":"            pass\n    return default_port\n\n\nasync def read_exact(reader: asyncio.StreamReader, n: int) -> bytes:\n    buf = b\"\"\n    while len(buf) < n:\n        chunk = await reader.read(n - len(buf))\n        if not chunk:\n            raise ConnectionError(\"Connection closed while reading\")\n        buf += chunk\n    return buf\n\n\nasync def read_frame(reader: asyncio.StreamReader) -> bytes:\n    header = await read_exact(reader, 8)\n    (length,) = struct.unpack(\">Q\", header)\n    if length <= 0 or length > (64 * 1024 * 1024):\n        raise ValueError(f\"Invalid frame length: {length}\")\n    return await read_exact(reader, length)\n\n\nasync def write_frame(writer: asyncio.StreamWriter, payload: bytes) -> None:\n    header = struct.pack(\">Q\", len(payload))\n    writer.write(header)\n    writer.write(payload)\n    await asyncio.wait_for(writer.drain(), timeout=TIMEOUT)\n\n\nasync def do_handshake(reader: asyncio.StreamReader) -> None:\n    # Server sends a single line handshake: \"WELCOME UNITY-MCP 1 FRAMING=1\\n\"\n    line = await reader.readline()\n    if not line or b\"WELCOME UNITY-MCP\" not in line:\n        raise ConnectionError(f\"Unexpected handshake from server: {line!r}\")\n\n\ndef make_ping_frame() -> bytes:","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/tools/stress_mcp.py#L49-L85","documentation":"Raised by read_frame() in stress_mcp.py after decoding the 8-byte big-endian length header to a value <=0 or >64MiB. This signals framing desynchronization — the bytes interpreted as a header are not a valid length prefix, meaning the read position is misaligned relative to the bridge's framed protocol.","triggerScenarios":"The reader is out of sync with the framed stream: connecting to a non-framed service (e.g. the HTTP MCP endpoint), leftover bytes from an incomplete prior read, or the bridge sent data the client didn't expect (e.g. an unsolicited notification) that shifted the byte boundary.","commonSituations":"Wrong port — connecting to the Python HTTP server instead of the Unity TCP bridge; a prior read_frame timed out (asyncio.wait_for) leaving partial header bytes in the StreamReader buffer that corrupt the next decode; protocol version mismatch where the bridge uses different framing.","solutions":["Verify the target port is the Unity TCP bridge (discover_port reads unity_port from status files), not the HTTP MCP port.","After a timeout on read_frame, discard and reconnect rather than reusing the desynchronized reader — the stress client already does this in its except block.","If the issue persists, inspect raw bytes with MCP_STRESS_DEBUG=1 to see what the bridge is actually sending."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    _ = await asyncio.wait_for(read_frame(reader), timeout=TIMEOUT)\nexcept (ValueError, ConnectionError, asyncio.TimeoutError):\n    # Stream is desynchronized or closed — reconnect from scratch.\n    if writer:\n        writer.close()\n        await writer.wait_closed()\n    reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=TIMEOUT)\n    await asyncio.wait_for(do_handshake(reader), timeout=TIMEOUT)","preventionTips":["Never reuse a StreamReader after a framing error — always reconnect.","Confirm the port is the TCP bridge (unity_port), not the HTTP server.","After a read timeout, treat the connection as poisoned and reconnect."],"tags":["stress-test","network","tcp","protocol","framing","unity-bridge"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}