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
- Check GetSession(id) for an existing session before creating/resuming one with the same id.
- Reuse the tracked session instead of re-registering it.
- Serialize session-creation calls (or await the first one) to avoid duplicate-id races.
- 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
- Look up existing sessions before create/resume
- Serialize session-creation calls to avoid duplicate-id races
- Keep a single source of truth for active session ids
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
- CopilotClient is in Mode = CopilotClientMode.Empty but the…
- Failed to delete session
- Failed to register GitHub token provider.
- LLM inference response StartAsync() called twice.
- No session found for sessionId
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)