github/copilot-sdk · error · ObjectDisposedException

Exception of type 'System.ObjectDisposedException' was…

Error message

Exception of type 'System.ObjectDisposedException' was thrown. (ObjectDisposedException(nameof(CopilotClient)))

What it means

The CopilotClient.Rpc property getter throws ObjectDisposedException(nameof(CopilotClient)) when the client has already been disposed. It guards access to the internal server RPC channel so callers never touch a dead connection.

Solutions

  1. Ensure all Rpc usage completes before disposing the client
  2. Check the client's disposed state before accessing Rpc
  3. Recreate a new CopilotClient (and StartAsync) if disposal was unintentional

Example fix

// before
var rpc = client.Rpc;
// after
if (client is { }) { var rpc = client.Rpc; } // only while client is alive/not disposed
Defensive patterns

Strategy: try-catch

Validate before calling

// no public disposed flag; track it at the call site
bool clientAlive = !disposedFlag;

Try / catch

try { var rpc = client.Rpc; } catch (ObjectDisposedException) { client = new CopilotClient(options); await client.StartAsync(); var rpc = client.Rpc; }

Prevention

When it happens

Trigger: Reading the Rpc property after calling DisposeAsync/Dispose on the CopilotClient.

Common situations: Background tasks or event handlers outliving the client's lifetime; accessing Rpc from a finalizer or after a cancellation-triggered shutdown; holding a stale client reference across a reconnect cycle.

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

Appendix: source

Thrown at dotnet/src/Client.cs:111

    /// <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)