github/copilot-sdk · error · RuntimeError
Process not started or stdout not available
Error message
Process not started or stdout not available
What it means
RuntimeError raised in CopilotClient's TCP-mode startup read_port() helper when the CLI subprocess handle is missing or its stdout stream is unavailable. The library needs to read the port announcement line from CLI stdout before it can connect, so without a live stdout it cannot proceed with TCP startup.
Solutions
- Ensure the CLI subprocess is started (via the library's normal start path) before connecting in TCP mode
- Verify the spawn options include stdout=PIPE (or equivalent) so the port announcement can be read
- If the process died, create a fresh CopilotClient/restart rather than reusing the stale instance
- Check that no custom subclass or monkeypatch clears self._process before the port wait runs
Example fix
// before await client._start_cli_server() # process never spawned -> RuntimeError // after await client.start() # library path spawns the CLI, then waits for port announcement
Defensive patterns
Strategy: try-catch
Validate before calling
if client._process is None or client._process.stdout is None:
raise RuntimeError("CLI must be started before TCP connect") Type guard
def cli_ready(client) -> bool:
return getattr(client, "_process", None) is not None and client._process.stdout is not None Try / catch
try:
await client.start()
except RuntimeError as e:
if "Process not started or stdout not available" in str(e):
await client.start() # fresh spawn via the official start path
else:
raise Prevention
- Always start the client through the public start() API
- Never reuse a client instance after stop()
- Confirm subprocess spawn includes stdout piping
- Avoid reaching into private _process state in application code
When it happens
Trigger: Calling start()/connect in TCP mode when self._process was never set (CLI not spawned) or was created without a stdout pipe, so read_port() sees process=None or process.stdout=None.
Common situations: Calling _start_cli_server before spawning the process; the process handle was cleared after a crash/stop; a custom spawn path built the Popen/asyncio process without stdout=PIPE; attempting restart after a previous failed start left stale state.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- CLI process exited before announcing port
- Timeout waiting for CLI server to start
- str(e)
- {process exit error from _get_process_exit_error()}
- Server port not available
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/843d5ac549886a75.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/client.py:4472
self._cli_process = self._process
log_timing(
logger,
logging.DEBUG,
"CopilotClient._start_cli_server subprocess spawned",
spawn_start,
)
# For stdio mode, we're ready immediately
if use_stdio:
return
# For TCP mode, wait for port announcement
loop = asyncio.get_event_loop()
process = self._process # Capture for closure
async def read_port():
if not process or not process.stdout:
raise RuntimeError("Process not started or stdout not available")
while True:
line = await loop.run_in_executor(None, process.stdout.readline)
if not line:
raise RuntimeError("CLI process exited before announcing port")
line_str = line.decode() if isinstance(line, bytes) else line
logger.debug("[CLI] %s", line_str.rstrip())
match = re.search(r"listening on port (\d+)", line_str, re.IGNORECASE)
if match:
self._runtime_port = int(match.group(1))
return
try:
port_wait_start = time.perf_counter()
await asyncio.wait_for(read_port(), timeout=10.0)
log_timing(
logger,
logging.DEBUG,View on GitHub (pinned to cd8cf15dc3)