github/copilot-sdk · error · RuntimeError

Failed to connect to CLI server at

Error message

Failed to connect to CLI server at {host}:{port}: {e}

What it means

Wraps socket errors raised by socket.create_connection in CopilotClient._connect_via_tcp. It means the TCP handshake to the CLI server at the given host:port failed - connection refused, wrong port, unreachable host, or the TCP_CONNECTION_TIMEOUT elapsed before the CLI started listening - with the original exception chained as the cause.

Solutions

  1. Retry the connect with a short delay (server may need a moment after announcement)
  2. Verify the host configuration matches the interface the CLI bound to (127.0.0.1 vs localhost vs 0.0.0.0)
  3. Check firewall/VPN rules for the chosen port range
  4. Confirm the CLI process is still alive when this error occurs
  5. Configure a fixed known port to avoid announcement/connect races

Example fix

// before
await client._connect_via_tcp()  # transient refusal right after startup

// after
for attempt in range(5):
    try:
        await client._connect_via_tcp()
        break
    except RuntimeError as e:
        if attempt == 4: raise
        await asyncio.sleep(0.5)
Defensive patterns

Strategy: retry

Validate before calling

import socket
sock = socket.socket()
if not connectable(client._actual_host, client._runtime_port):
    schedule_retry()

Try / catch

try:
    await client._connect_via_tcp()
except RuntimeError as e:
    if "Failed to connect to CLI server" in str(e):
        await asyncio.sleep(0.5)
        await client._connect_via_tcp()  # bounded retry loop recommended
    else:
        raise

Prevention

When it happens

Trigger: socket.create_connection raises OSError during _connect_via_tcp: server not listening on the announced port, wrong host, firewall blocking, IPv6/IPv4 resolution issues, or the CLI died right after announcing its port.

Common situations: Port announced but server crashed immediately (race); connecting to 'localhost' when the server bound only to 127.0.0.1 or ::1; firewall/VPN blocking the port; containers where host differs; port already reused by another process.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/0684e752ba706438. Report an issue: GitHub.

Appendix: source

Thrown at python/copilot/client.py:4674

            tcp_connect_start = time.perf_counter()
            logger.info(
                "CopilotClient._connect_via_tcp connecting to CLI server",
                extra={"host": self._actual_host, "port": self._runtime_port},
            )
            sock = socket.create_connection(
                (self._actual_host, self._runtime_port), timeout=TCP_CONNECTION_TIMEOUT
            )
            sock.settimeout(None)  # Remove timeout after connection
            log_timing(
                logger,
                logging.DEBUG,
                "CopilotClient._connect_via_tcp TCP connect complete",
                tcp_connect_start,
                host=self._actual_host,
                port=self._runtime_port,
            )
        except OSError as e:
            raise RuntimeError(
                f"Failed to connect to CLI server at {self._actual_host}:{self._runtime_port}: {e}"
            )

        # Create a file-like wrapper for the socket
        sock_file = sock.makefile("rwb", buffering=0)

        # Create a mock process object that JsonRpcClient expects
        class SocketWrapper:
            def __init__(self, sock_file, sock_obj):
                self.stdin = sock_file
                self.stdout = sock_file
                self.stderr = None
                self._socket = sock_obj

            def terminate(self):
                import socket as _socket_mod

                # shutdown() sends TCP FIN to the server (triggering

View on GitHub (pinned to cd8cf15dc3)