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

  1. Ensure the CLI subprocess is started (via the library's normal start path) before connecting in TCP mode
  2. Verify the spawn options include stdout=PIPE (or equivalent) so the port announcement can be read
  3. If the process died, create a fresh CopilotClient/restart rather than reusing the stale instance
  4. 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

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


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)