Skip to content

Commit f0e49c2

Browse files
Jason Robertclaude
andcommitted
fix(copilot): clear O_NONBLOCK on subprocess pipes to prevent BlockingIOError
Large JSON-RPC messages (e.g., prompts with many gathered articles) can exceed the OS pipe buffer. When the asyncio event loop sets O_NONBLOCK on inherited file descriptors, writes raise BlockingIOError instead of blocking until the reader drains the pipe. This adds a post-start fixup that clears O_NONBLOCK on stdin/stdout of the Copilot CLI subprocess, since the SDK already runs writes in a thread-pool executor where blocking is safe and correct. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 930a057 commit f0e49c2

1 file changed

Lines changed: 33 additions & 0 deletions

File tree

src/conductor/providers/copilot.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,39 @@ async def _ensure_client_started(self) -> None:
13731373
await self._client.start()
13741374
self._started = True
13751375

1376+
# Ensure subprocess pipes are in blocking mode to prevent
1377+
# BlockingIOError on large payloads. The asyncio event loop
1378+
# may set O_NONBLOCK on inherited file descriptors.
1379+
self._fix_pipe_blocking_mode()
1380+
1381+
def _fix_pipe_blocking_mode(self) -> None:
1382+
"""Clear O_NONBLOCK on the Copilot CLI subprocess pipes.
1383+
1384+
Large JSON-RPC messages (e.g., prompts with many gathered articles)
1385+
can exceed the OS pipe buffer. When O_NONBLOCK is set, writes raise
1386+
BlockingIOError instead of blocking until the reader drains the pipe.
1387+
Since the SDK already runs writes in a thread-pool executor, blocking
1388+
is safe and correct here.
1389+
"""
1390+
import fcntl
1391+
import os
1392+
1393+
process = getattr(self._client, "_process", None)
1394+
if not process:
1395+
return
1396+
1397+
for name, stream in [("stdin", process.stdin), ("stdout", process.stdout)]:
1398+
if stream is None:
1399+
continue
1400+
try:
1401+
fd = stream.fileno()
1402+
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
1403+
if flags & os.O_NONBLOCK:
1404+
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
1405+
logger.debug(f"Cleared O_NONBLOCK on Copilot CLI {name}")
1406+
except (OSError, ValueError):
1407+
pass # fd may already be closed or invalid
1408+
13761409
def _calculate_delay(self, attempt: int, config: RetryConfig) -> float:
13771410
"""Calculate delay with exponential backoff and jitter.
13781411

0 commit comments

Comments
 (0)