github/copilot-sdk · critical · IOException

Communication error with Copilot CLI

Error message

Communication error with Copilot CLI: {ex.Message}

What it means

Thrown as IOException when a generic (non-RPC-protocol) communication exception occurs while talking to the Copilot CLI and no stderr output was captured. The original exception message is embedded so the underlying cause (stream closed, pipe broken, etc.) is visible, and it is wrapped as the inner exception.

Solutions

  1. Check the InnerException for the true transport failure
  2. Verify the CLI process is still alive (IsAlive/process handle) and restart the client/connection if not
  3. Add retry logic around transient RPC calls with a new client instance on failure
  4. Capture the CLI's own logs to find why the transport broke; upgrade the CLI if it crashes silently

Example fix

// before
var resp = await client.SendAsync(req);
// after
try { var resp = await client.SendAsync(req); }
catch (IOException ex) when (ex.InnerException is not null) { logger.LogWarning(ex.InnerException, "transport failure"); await RestartClientAsync(); }
Defensive patterns

Strategy: retry

Validate before calling

if (!client.IsConnected) { client = await ReconnectAsync(); }

Try / catch

try { return await client.SendAsync(req); }
catch (IOException ex) when (IsTransient(ex)) { await Task.Delay(backoff); return await RetryWithNewClientAsync(req); }

Prevention

When it happens

Trigger: An IOException (or similar communication exception) occurs during an RPC call while the CLI process is running or its stderr is empty — e.g. the stdio pipe/transport broke mid-request without the process printing anything.

Common situations: CLI process killed abruptly (SIGKILL) without flushing stderr; broken pipe when the OS terminated the child; transport deserialization/handler exceptions surfaced as communication errors.

Related errors


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

Appendix: source

Thrown at dotnet/src/Client.cs:1976

            return await rpc.InvokeAsync<T>(method, args, cancellationToken, onResponseInline);
        }
        catch (ConnectionLostException ex)
        {
            string? stderrOutput = null;
            if (stderrBuffer is not null)
            {
                lock (stderrBuffer)
                {
                    stderrOutput = stderrBuffer.ToString().Trim();
                }
            }

            if (!string.IsNullOrEmpty(stderrOutput))
            {
                throw new IOException(FormatCliExitedMessage("CLI process exited unexpectedly.", stderrOutput!), ex);
            }

            throw new IOException($"Communication error with Copilot CLI: {ex.Message}", ex);
        }
        catch (RemoteRpcException ex)
        {
            throw new IOException($"Communication error with Copilot CLI: {ex.Message}", ex);
        }
    }

    private static string FormatCliExitedMessage(string message, string stderrOutput)
    {
        return string.IsNullOrEmpty(stderrOutput)
            ? message
            : $"{message}\nstderr: {stderrOutput}";
    }

    [LoggerMessage(
        Level = LogLevel.Information,
        Message = "CopilotClient.StartCliServerAsync starting Copilot CLI. CliPath={CliPath}, Executable={Executable}, CliPathSource={CliPathSource}, UseStdio={UseStdio}, Port={Port}")]
    private static partial void LogStartingCopilotCli(ILogger logger, string cliPath, string executable, string cliPathSource, bool useStdio, int? port);

View on GitHub (pinned to cd8cf15dc3)