github/copilot-sdk · error · InvalidOperationException

Session ' ' is already tracked by this client.

Error message

Session '{session.SessionId}' is already tracked by this client.

What it means

RegisterSession adds a CopilotSession to the client's concurrent session map and throws InvalidOperationException if a session with the same SessionId is already tracked (ConcurrentDictionary.TryAdd returned false). It protects the invariant that each session id maps to exactly one session object.

Solutions

  1. Check GetSession(id) for an existing session before creating/resuming one with the same id.
  2. Reuse the tracked session instead of re-registering it.
  3. Serialize session-creation calls (or await the first one) to avoid duplicate-id races.
  4. If the id is server-assigned, log/report the duplicate — it indicates a server-side id collision.

Example fix

// before
var s1 = await client.CreateSessionAsync();
var s2 = await client.ResumeSessionAsync(s1.SessionId); // already tracked

// after
var session = client.GetSession(s1.SessionId) ?? await client.ResumeSessionAsync(s1.SessionId);
Defensive patterns

Strategy: validation

Validate before calling

if (client.GetSession(sessionId) is not null)
    return; // already tracked; reuse instead of registering again

Type guard

bool AlreadyTracked(CopilotClient c, string id) => c.GetSession(id) is not null;

Try / catch

try { RegisterSession(session); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already tracked"))
{ /* reuse existing session */ }

Prevention

When it happens

Trigger: Calling an internal/registration path (directly or via resume flows) that registers a session whose SessionId already exists in the client's _sessions dictionary.

Common situations: Calling session-creation or resume APIs twice for the same session id; a race where two threads create sessions that the server assigned the same id; re-registering a session after a partial failure rolled back inconsistently.

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

Appendix: source

Thrown at dotnet/src/Client.cs:2802

        options.TypeInfoResolverChain.Add(SessionEventsJsonContext.Default);
        options.TypeInfoResolverChain.Add(GitHub.Copilot.Rpc.RpcJsonContext.Default);

        options.MakeReadOnly();

        return options;
    }

    internal CopilotSession? GetSession(string sessionId)
    {
        _sessions.TryGetValue(sessionId, out var session);
        return session;
    }

    private void RegisterSession(CopilotSession session)
    {
        if (!_sessions.TryAdd(session.SessionId, session))
        {
            throw new InvalidOperationException($"Session '{session.SessionId}' is already tracked by this client.");
        }
    }

    /// <summary>
    /// Disposes the <see cref="CopilotClient"/> synchronously.
    /// </summary>
    /// <remarks>
    /// Prefer using <see cref="DisposeAsync"/> for better performance in async contexts.
    /// </remarks>
    public void Dispose()
    {
        DisposeAsync().AsTask().GetAwaiter().GetResult();
    }

    /// <summary>
    /// Disposes the <see cref="CopilotClient"/> asynchronously.
    /// </summary>
    /// <returns>A <see cref="ValueTask"/> representing the asynchronous dispose operation.</returns>

View on GitHub (pinned to cd8cf15dc3)