resumeSession() on already-active session causes doubled events — SDK should guard

Open Beginner friendly
#742 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
node.js, typescript
Domain
api

Research direction

Start in client.ts at resumeSession() and inspect the local sessions Map and the existing session registration methods. Reproduce the supplied createSession/resumeSession sequence, then verify that an already-active session does not produce doubled notifications while genuinely new sessions still resume normally.

Written by the indexing model from the issue text.

Description

Problem

Calling client.resumeSession(sessionId) on a session that's already active (created via createSession() on the same connection) causes all subsequent session.event notifications to fire twice. This is a server-side issue (the CLI registers a second event subscription without deduplicating), but the SDK can and should protect callers.

Reproduction

const { CopilotClient, approveAll } = await import('@github/copilot-sdk');
const client = new CopilotClient({ cwd: process.cwd(), autoStart: true });

const session = await client.createSession({
    model: 'claude-sonnet-4-5',
    onPermissionRequest: approveAll,
});

// Events are single here ✅
let count1 = 0;
const unsub1 = session.on(() => count1++);
await session.sendAndWait({ prompt: 'Say hello' });
unsub1();

// Resume the SAME active session
const resumed = await client.resumeSession(session.sessionId, {
    onPermissionRequest: approveAll,
});

// Events are now doubled ❌
let count2 = 0;
const unsub2 = resumed.on(() => count2++);
await resumed.sendAndWait({ prompt: 'Say hello again' });
unsub2();

console.log(count1, count2); // ~9, ~17

Suggested Fix

In client.ts resumeSession(), check if the session already exists in the local sessions Map before sending session.resume to the server:

async resumeSession(sessionId: string, config: ResumeSessionConfig): Promise<CopilotSession> {
    // Guard: if we already have a live session for this ID, return it
    const existing = this.sessions.get(sessionId);
    if (existing) {
        // Re-register handlers if config changed
        existing.registerTools(config.tools);
        existing.registerPermissionHandler(config.onPermissionRequest);
        if (config.hooks) existing.registerHooks(config.hooks);
        return existing;
    }

    // ... existing resumeSession logic for truly new sessions
}

This prevents the server-side duplicate subscription while still allowing callers to update tools/permissions. Callers who genuinely want a fresh session can destroy() first.

Impact

Any SDK caller that uses resumeSession() as a health check (verify session is alive before sending a message) gets silently broken — doubled events for the rest of the session. We discovered this when implementing plan-mode session recovery in a VS Code extension.

Workaround

We use session.abort() as a lightweight liveness check instead of resumeSession(). abort() is a no-op on idle sessions and throws "Session not found" if the session was garbage-collected. Same signal, no side effects.

Environment

  • SDK: v0.1.22
  • CLI: v0.0.421
  • Node: v24.13.1
Dominant language
Java
Stars
10.5k
Forks
1.5k
Avg merge
1d 12h
Merged PRs (30d)
133

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from github/copilot-sdk

All issues in github/copilot-sdk

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.