I am trying to use the equivalent of copilot --continue in the CLI to give a conversational agent continuity of context between sessions. copilot made an implementation but it didn't work because the SDK wasn't working as expected. I asked copilot to make a bug report with a reproducible example, which I include below:
Summary
CopilotClient.resumeSession() from @github/copilot-sdk resolves successfully but the returned session object does not process new messages. Calling session.send() on a resumed session completes without error, but no assistant.message_delta events are ever emitted. The session.idle event fires repeatedly (replayed from prior turn state), but no new response is generated.
Details
CopilotClient.resumeSession() resolves successfully but the returned session object does not process new messages. Calling session.send() on a resumed session completes without error, but no assistant.message_delta events are ever emitted. The session.idle event fires repeatedly (apparently replayed from the prior turn's state), but no new response is generated.
Environment
- SDK:
@github/copilot-sdk ^0.1.25
- Node.js: 22.x+ (required for
node:sqlite)
- Model:
claude-sonnet-4 (also tested with default model)
- Session config:
streaming: true, infiniteSessions: { enabled: true }
Steps to Reproduce
Minimal reproduction script
See resume-session-repro.ts below for a self-contained script. Run with:
npx tsx bug-reports/resume-session-repro.ts
Manual steps
- Create a
CopilotClient and call client.start().
- Create a session with
client.createSession({ model: "claude-sonnet-4", streaming: true }).
- Send a message with
session.send({ prompt: "Hello" }). Observe that assistant.message_delta events fire and a response is received. ✅
- Save the
session.sessionId.
- Call
client.resumeSession(sessionId). It resolves successfully. ✅
- Subscribe to events on the resumed session and call
session.send({ prompt: "Hello again" }).
- Observe: no
assistant.message_delta events fire. ❌
- Observe:
session.idle fires immediately (or repeatedly), with no preceding delta events. ❌
Expected Behavior
After resumeSession(), the session should be fully functional. session.send() should produce assistant.message_delta events followed by assistant.message and session.idle, exactly as on a fresh session.
Actual Behavior
resumeSession() resolves without error
session.send() returns a promise that does not reject
session.idle fires prematurely — before any response events
assistant.message_delta is never emitted
assistant.message may fire with stale content from the prior turn (replayed)
tool.execution_start may also fire with stale data from the prior turn
- The session is effectively dead; no new LLM inference occurs
Impact
This makes resumeSession() unusable for maintaining conversation continuity across reconnects. In our application (a web chat UI), users who disconnect and reconnect see no output from the agent until the system times out and falls back to a new session.
Observed Server Logs
[Web] Attempting to resume session 29f33291-2b36-4c70-b640-ec70f0fea4a4 for David
[Web] Resumed session 29f33291-2b36-4c70-b640-ec70f0fea4a4 for David
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Web] Resume attempt 1 failed for David: Resume attempt timeout
[Web] Retry 1/2 for resumed session (David)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Web] Resume attempt 2 failed for David: Resume attempt timeout
[Web] Retry 2/2 for resumed session (David)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Core] Ignoring premature session.idle (no response events received yet)
[Web] Resume attempt 3 failed for David: Resume attempt timeout
[Web] Resumed session unresponsive after 3 attempts for David, falling back to new session
Note: session.idle fires multiple times per attempt with no message_delta events in between. The resumed session is completely unresponsive to new prompts.
Impact
Session resume is currently disabled in Descartes conversational agent. Every reconnect creates a new session, losing conversation history.
Repro file: resume-session-repro.ts (upload failed so pasting here)
/**
* Reproduction script for @github/copilot-sdk resumeSession bug.
*
* Demonstrates that a session resumed via client.resumeSession() does not
* process new messages: session.send() completes but no assistant.message_delta
* events fire and session.idle fires prematurely (without a new response).
*
* Usage:
* npx tsx bug-reports/resume-session-repro.ts
*
* Requirements:
* - Node.js >= 22.x (SDK requires node:sqlite)
* - @github/copilot-sdk ^0.1.25
* - Authenticated GitHub Copilot token (via `copilot auth login` or env)
*/
import { CopilotClient, CopilotSession } from "@github/copilot-sdk";
const TIMEOUT_MS = 20000;
async function sendAndCollect(
session: CopilotSession,
prompt: string,
label: string
): Promise<string> {
return new Promise((resolve, reject) => {
let content = "";
let gotDelta = false;
const unsubs: (() => void)[] = [];
const cleanup = () => unsubs.forEach((fn) => fn());
unsubs.push(
session.on("assistant.message_delta", (event) => {
gotDelta = true;
if (event.data.deltaContent) {
content += event.data.deltaContent;
process.stdout.write(".");
}
})
);
unsubs.push(
session.on("assistant.message", (event) => {
if (event.data.content) {
content = event.data.content;
}
})
);
unsubs.push(
session.on("session.idle", () => {
console.log(
`\n[${label}] session.idle fired — gotDelta: ${gotDelta}, content length: ${content.length}`
);
if (!gotDelta) {
console.log(`[${label}] ⚠️ IDLE WITHOUT ANY DELTAS — session is unresponsive`);
// Don't resolve yet; let timeout handle it
return;
}
cleanup();
resolve(content);
})
);
unsubs.push(
session.on("session.error", (event) => {
cleanup();
reject(new Error(`[${label}] Session error: ${event.data.message}`));
})
);
const timer = setTimeout(() => {
cleanup();
if (gotDelta) {
resolve(content);
} else {
reject(new Error(`[${label}] Timed out after ${TIMEOUT_MS}ms with no response`));
}
}, TIMEOUT_MS);
unsubs.push(() => clearTimeout(timer));
console.log(`[${label}] Sending: "${prompt}"`);
session.send({ prompt }).catch(reject);
});
}
async function main() {
console.log("=== @github/copilot-sdk resumeSession Bug Reproduction ===\n");
// Step 1: Create a client
console.log("[1] Creating CopilotClient...");
const client = new CopilotClient();
await client.start();
console.log("[1] Client started\n");
// Step 2: Create a fresh session
console.log("[2] Creating session...");
const session = await client.createSession({
model: "claude-sonnet-4",
streaming: true,
});
const sessionId = session.sessionId;
console.log(`[2] Session created: ${sessionId}\n`);
// Step 3: Send a message on the fresh session — this should work
console.log("[3] Sending message on fresh session...");
try {
const response1 = await sendAndCollect(
session,
"Say exactly: Hello, session works!",
"FRESH"
);
console.log(`[3] ✅ Fresh session response (${response1.length} chars): "${response1.slice(0, 100)}"\n`);
} catch (err) {
console.log(`[3] ❌ Fresh session failed: ${(err as Error).message}\n`);
process.exit(1);
}
// Step 4: Resume the same session
console.log("[4] Resuming session...");
let resumed: CopilotSession;
try {
resumed = await client.resumeSession(sessionId);
console.log(`[4] Session resumed: ${resumed.sessionId}\n`);
} catch (err) {
console.log(`[4] ❌ Resume failed: ${(err as Error).message}`);
console.log(" (This may happen if the session expired — try running again quickly)\n");
process.exit(1);
}
// Step 5: Send a message on the resumed session — this is expected to fail
console.log("[5] Sending message on resumed session...");
try {
const response2 = await sendAndCollect(
resumed,
"Say exactly: Hello, resume works!",
"RESUMED"
);
console.log(`[5] ✅ Resumed session response (${response2.length} chars): "${response2.slice(0, 100)}"\n`);
console.log("=== RESULT: resumeSession is WORKING ===");
} catch (err) {
console.log(`[5] ❌ Resumed session failed: ${(err as Error).message}\n`);
console.log("=== RESULT: resumeSession is BROKEN ===");
console.log("The resumed session accepted send() but produced no message_delta events.");
console.log("session.idle fires prematurely (replayed from prior turn state).");
}
process.exit(0);
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});
I am trying to use the equivalent of
copilot --continuein the CLI to give a conversational agent continuity of context between sessions. copilot made an implementation but it didn't work because the SDK wasn't working as expected. I asked copilot to make a bug report with a reproducible example, which I include below:Summary
CopilotClient.resumeSession()from@github/copilot-sdkresolves successfully but the returned session object does not process new messages. Callingsession.send()on a resumed session completes without error, but noassistant.message_deltaevents are ever emitted. Thesession.idleevent fires repeatedly (replayed from prior turn state), but no new response is generated.Details
CopilotClient.resumeSession()resolves successfully but the returned session object does not process new messages. Callingsession.send()on a resumed session completes without error, but noassistant.message_deltaevents are ever emitted. Thesession.idleevent fires repeatedly (apparently replayed from the prior turn's state), but no new response is generated.Environment
@github/copilot-sdk^0.1.25node:sqlite)claude-sonnet-4(also tested with default model)streaming: true,infiniteSessions: { enabled: true }Steps to Reproduce
Minimal reproduction script
See
resume-session-repro.tsbelow for a self-contained script. Run with:Manual steps
CopilotClientand callclient.start().client.createSession({ model: "claude-sonnet-4", streaming: true }).session.send({ prompt: "Hello" }). Observe thatassistant.message_deltaevents fire and a response is received. ✅session.sessionId.client.resumeSession(sessionId). It resolves successfully. ✅session.send({ prompt: "Hello again" }).assistant.message_deltaevents fire. ❌session.idlefires immediately (or repeatedly), with no preceding delta events. ❌Expected Behavior
After
resumeSession(), the session should be fully functional.session.send()should produceassistant.message_deltaevents followed byassistant.messageandsession.idle, exactly as on a fresh session.Actual Behavior
resumeSession()resolves without errorsession.send()returns a promise that does not rejectsession.idlefires prematurely — before any response eventsassistant.message_deltais never emittedassistant.messagemay fire with stale content from the prior turn (replayed)tool.execution_startmay also fire with stale data from the prior turnImpact
This makes
resumeSession()unusable for maintaining conversation continuity across reconnects. In our application (a web chat UI), users who disconnect and reconnect see no output from the agent until the system times out and falls back to a new session.Observed Server Logs
Note:
session.idlefires multiple times per attempt with nomessage_deltaevents in between. The resumed session is completely unresponsive to new prompts.Impact
Session resume is currently disabled in Descartes conversational agent. Every reconnect creates a new session, losing conversation history.
Repro file:
resume-session-repro.ts(upload failed so pasting here)