-
Notifications
You must be signed in to change notification settings - Fork 819
Description
Description
When using CopilotClient.resumeSession() for multi-turn conversations, each assistant.message_delta event is emitted N+1 times, where N is the number of times the session has been resumed. The duplicates share the same event.id.
createSession+ first message: 1x per delta (correct)- After 1st
resumeSession: 2x per delta - After 2nd
resumeSession: 3x per delta - After Nth
resumeSession: (N+1)x per delta
Reproduction
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient({ githubToken: TOKEN });
// First turn — works correctly
let session = await client.createSession({
sessionId: "test-session",
model: "gpt-4.1",
streaming: true,
});
session.on("assistant.message_delta", (event) => {
console.log("DELTA:", event.id, event.data?.deltaContent);
});
await session.sendAndWait({ prompt: "Say hello" });
// Each delta fires 1x ✅
// Second turn — deltas duplicated
session = await client.resumeSession("test-session", {
model: "gpt-4.1",
streaming: true,
});
session.on("assistant.message_delta", (event) => {
console.log("DELTA:", event.id, event.data?.deltaContent);
});
await session.sendAndWait({ prompt: "Say goodbye" });
// Each delta fires 2x ❌ (same event.id on both copies)Observed behavior (server logs)
After the 1st resume (2nd message), each delta arrives exactly twice:
DELTA #1 id=… size=1056 "En una pequeña"
DELTA #2 id=… size=1056 "En una pequeña" ← duplicate
DELTA #3 id=… size=1098 " aldea rodeada"
DELTA #4 id=… size=1098 " aldea rodeada" ← duplicate
After the 2nd resume (3rd message), each delta arrives 3 times:
DELTA #1 id=… "Había"
DELTA #2 id=… "Había" ← duplicate
DELTA #3 id=… "Había" ← duplicate
SDK source analysis
I inspected the SDK wrapper code (@github/copilot-sdk@0.1.23) and the dispatch path appears clean:
attachConnectionHandlers()— registersonNotification("session.event")once per connection (not per session).handleSessionEventNotification()— does a singlethis.sessions.get(sessionId)lookup and callssession._dispatchEvent(event)once.resumeSession()— creates a newCopilotSessionobject and overwrites the entry inthis.sessionsMap, so only one session object exists per ID.CopilotSession._dispatchEvent()— iterates typed handlers + wildcard handlers once each, no accumulation.
Since the SDK client-side dispatch path invokes _dispatchEvent exactly once per incoming JSON-RPC notification, the duplication appears to originate in the CLI server subprocess (@github/copilot package) which sends N+1 session.event JSON-RPC notifications per logical delta after N resumes.
Workaround
Deduplicate using event.id, which is shared across all copies of the same logical delta:
const seenEventIds = new Set();
const unsubscribe = session.on("assistant.message_delta", (event) => {
if (event.id && seenEventIds.has(event.id)) return;
if (event.id) seenEventIds.add(event.id);
// Process delta normally
console.log(event.data?.deltaContent);
});Environment
@github/copilot-sdk: 0.1.23- Node.js: v22.x
- OS: macOS