Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 16, 2026

Python's read(n) on pipes doesn't guarantee n bytes—when data exceeds the pipe buffer (~64KB), the kernel returns only what's buffered. This caused JSON parsing failures on large responses.

Changes

  • Added _read_exact() method: Loops until all requested bytes are read, accumulating chunks from potentially partial read() calls
  • Updated _read_message(): Replaces direct read(content_length) with _read_exact(content_length)
  • Added unit tests: Coverage for 64KB boundary, 70KB/80KB/100KB payloads, EOFError cases, and sequential large messages

Reproduction

# Before: fails with "Unterminated string" when response exceeds ~64KB
context = "Status update: work in progress. " * 1000  # ~35KB
await session.send({"prompt": f"Summarize: {context}"})  # Response ~72KB

# After: handles any size payload correctly

The fix handles the POSIX behavior where pipe read() may return fewer bytes than requested, particularly when crossing buffer boundaries.

Original prompt

This section details on the original issue you should resolve

<issue_title>JSON-RPC read fails with large payloads (>64KB) due to pipe short reads</issue_title>
<issue_description>## Summary

The JSON-RPC client in copilot/jsonrpc.py fails to read large responses (>64KB) because _read_message() assumes read(n) always returns exactly n bytes. On Unix pipes, this is not guaranteed—the kernel may return fewer bytes ("short read"), especially when data exceeds the pipe buffer size.

Reproduction

Send a prompt with ~35KB+ of context. The response (~72KB with context echo) exceeds the 64KB pipe buffer:

import asyncio
from copilot import CopilotClient

async def test():
    client = CopilotClient()
    await client.start()
    
    session = await client.create_session({
        "model": "claude-sonnet-4.5",
        "streaming": False,
        "available_tools": [],
    })
    
    # 35KB context triggers the bug
    context = "Status update: work in progress. " * 1000  # ~35KB
    await session.send({"prompt": f"Summarize: {context}"})
    # ... wait for response
    
asyncio.run(test())

Error:

JSON-RPC read loop error: Unterminated string starting at: line 1 column 36026 (char 36025)

Root Cause

In copilot/jsonrpc.py, the _read_message() method:

content_length = int(header.split(":")[1].strip())
# ...
content_bytes = self.process.stdout.read(content_length)  # BUG: assumes full read
content = content_bytes.decode("utf-8")
return json.loads(content)

When content_length exceeds the pipe buffer (64KB on macOS, varies on Linux), read() returns only what is currently buffered. The truncated bytes fail JSON parsing.

Evidence

Diagnostic testing shows the exact behavior:

Context Size Expected Response Received Missing
35KB 72,094 bytes 65,513 bytes 9.1%
40KB 82,258 bytes 65,514 bytes 20.4%
45KB 92,554 bytes 65,512 bytes 29.2%

Note: Received bytes are consistently ~65,512 (≈64KB), the macOS pipe buffer limit.

Suggested Fix

Read in a loop until all expected bytes are received:

def _read_exact(self, num_bytes: int) -> bytes:
    """Read exactly num_bytes, handling partial/short reads from pipes."""
    chunks = []
    remaining = num_bytes
    while remaining > 0:
        chunk = self.process.stdout.read(remaining)
        if not chunk:
            raise EOFError("Unexpected end of stream while reading JSON-RPC message")
        chunks.append(chunk)
        remaining -= len(chunk)
    return b"".join(chunks)

def _read_message(self) -> Optional[dict]:
    # ... existing header parsing ...
    content_bytes = self._read_exact(content_length)  # Use loop-based read
    # ... rest unchanged ...

Environment

  • OS: macOS 14.x (also affects Linux with different buffer sizes)
  • Python: 3.11
  • SDK version: Latest from pip

Workaround

Limit context size to ~30KB to keep responses under 64KB.

References

  • POSIX read() specification: reads from pipes may return fewer bytes than requested
  • macOS pipe buffer: 64KB (PIPE_SIZE in XNU kernel)
  • Linux pipe buffer: typically 64KB (configurable via fcntl(F_SETPIPE_SZ))</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits January 16, 2026 15:59
- Add _read_exact() method to handle short reads from pipes
- Update _read_message() to use _read_exact() for reliable large payload handling
- Add comprehensive unit tests for short read scenarios and large payloads

Co-authored-by: SteveSandersonMS <[email protected]>
Copilot AI changed the title [WIP] Fix JSON-RPC read issue for large payloads Fix JSON-RPC pipe reads >64KB by handling short reads Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON-RPC read fails with large payloads (>64KB) due to pipe short reads

2 participants