github/copilot-sdk · error · InvalidOperationException

Client is not started. Call StartAsync first.

Error message

Client is not started. Call StartAsync first.

What it means

CopilotClient.Rpc throws InvalidOperationException because the internal _serverRpc is null — StartAsync was never called (or has not completed) before the property was accessed. The docs require calling StartAsync before using Rpc.

Solutions

  1. Call await client.StartAsync() before reading Rpc
  2. Ensure StartAsync completed successfully (await its task and check for exceptions)
  3. If start failed, fix the underlying startup error and retry StartAsync

Example fix

// before
var client = new CopilotClient(options);
var rpc = client.Rpc;
// after
var client = new CopilotClient(options);
await client.StartAsync();
var rpc = client.Rpc;
Defensive patterns

Strategy: try-catch

Validate before calling

// _serverRpc is private; enforce start-before-use at the call site
if (!started) throw new InvalidOperationException("Call StartAsync first");

Try / catch

try { var rpc = client.Rpc; } catch (InvalidOperationException ex) when (ex.Message.Contains("StartAsync")) { await client.StartAsync(); var rpc = client.Rpc; }

Prevention

When it happens

Trigger: Accessing client.Rpc immediately after constructing CopilotClient, or before awaiting StartAsync, or after StartAsync failed and left the channel unassigned.

Common situations: Forgetting to await the StartAsync task; firing-and-forgetting StartAsync; using the client in a constructor where async startup hasn't finished; start failure swallowed upstream.

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/a2492514d4076707. Report an issue: GitHub.

Appendix: source

Thrown at dotnet/src/Client.cs:112

    /// <summary>
    /// Client-global RPC handlers (e.g. the LLM inference provider adapter),
    /// built once at construction and registered on every connection.
    /// </summary>
    private readonly ClientGlobalApiHandlers? _clientGlobalApis;

    private sealed record LifecycleSubscription(Type EventType, Action<SessionLifecycleEvent> Handler);

    /// <summary>
    /// Gets the typed RPC client for server-scoped methods (no session required).
    /// </summary>
    /// <remarks>
    /// The client must be started before accessing this property. Call <see cref="StartAsync"/> before use.
    /// </remarks>
    /// <exception cref="ObjectDisposedException">Thrown if the client has been disposed.</exception>
    /// <exception cref="InvalidOperationException">Thrown if the client is not started.</exception>
    public ServerRpc Rpc => _disposed
        ? throw new ObjectDisposedException(nameof(CopilotClient))
        : _serverRpc ?? throw new InvalidOperationException("Client is not started. Call StartAsync first.");

    /// <summary>
    /// Gets the actual TCP port the runtime is listening on, if using TCP transport.
    /// </summary>
    public int? RuntimePort => _actualPort;

    /// <summary>
    /// Creates a new instance of <see cref="CopilotClient"/>.
    /// </summary>
    /// <param name="options">Options for creating the client. If null, default options are used.</param>
    /// <example>
    /// <code>
    /// // Default options - spawns the bundled runtime using stdio
    /// var client = new CopilotClient();
    ///
    /// // Connect to an existing runtime
    /// var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri("localhost:3000") });
    ///

View on GitHub (pinned to cd8cf15dc3)