Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/conductor/providers/copilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,39 @@ async def _ensure_client_started(self) -> None:
await self._client.start()
self._started = True

# Ensure subprocess pipes are in blocking mode to prevent
# BlockingIOError on large payloads. The asyncio event loop
# may set O_NONBLOCK on inherited file descriptors.
self._fix_pipe_blocking_mode()

def _fix_pipe_blocking_mode(self) -> None:
"""Clear O_NONBLOCK on the Copilot CLI subprocess pipes.

Large JSON-RPC messages (e.g., prompts with many gathered articles)
can exceed the OS pipe buffer. When O_NONBLOCK is set, writes raise
BlockingIOError instead of blocking until the reader drains the pipe.
Since the SDK already runs writes in a thread-pool executor, blocking
is safe and correct here.
"""
import fcntl
import os

process = getattr(self._client, "_process", None)
if not process:
return

for name, stream in [("stdin", process.stdin), ("stdout", process.stdout)]:
if stream is None:
continue
try:
fd = stream.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
if flags & os.O_NONBLOCK:
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
logger.debug(f"Cleared O_NONBLOCK on Copilot CLI {name}")
except (OSError, ValueError):
pass # fd may already be closed or invalid

def _calculate_delay(self, attempt: int, config: RetryConfig) -> float:
"""Calculate delay with exponential backoff and jitter.

Expand Down
Loading