github/copilot-sdk · error

joinSession() is intended for extensions running as child…

Error message

joinSession() is intended for extensions running as child processes of the Copilot CLI.

What it means

joinSession() connects an extension back to the Copilot CLI session that spawned it. That link is only meaningful for child processes, which the CLI identifies via the SESSION_ID environment variable it injects. Without SESSION_ID there is no parent session to join, so the call fails immediately.

Solutions

  1. For standalone apps, create a session explicitly with CopilotClient instead of joinSession().
  2. Run the extension through the Copilot CLI so it is spawned as a child process with SESSION_ID set.
  3. In development, launch the extension via the CLI extension mechanism rather than invoking the file directly.
  4. Optionally check process.env.SESSION_ID first and fall back to a CopilotClient flow when absent.

Example fix

// before
const session = await joinSession({ tools }); // throws outside CLI child process
// after
if (!process.env.SESSION_ID) {
  const client = new CopilotClient();
  var session = await client.createSession({ tools });
} else {
  var session = await joinSession({ tools });
}
Defensive patterns

Strategy: validation

Validate before calling

if (!process.env.SESSION_ID) {
  throw new Error('Not running as a Copilot CLI child process; use CopilotClient instead of joinSession()');
}
const session = await joinSession(config);

Type guard

const runsInsideCopilotCli = (): boolean =>
  typeof process.env.SESSION_ID === 'string' && process.env.SESSION_ID.length > 0;

Try / catch

try {
  const session = await joinSession(config);
} catch (e) {
  if (e instanceof Error && e.message.includes('child processes of the Copilot CLI')) {
    const client = new CopilotClient();
    const session = await client.createSession(config);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling joinSession() from a normal shell, script, CI job, or any process not launched as a child process of the Copilot CLI (SESSION_ID unset).

Common situations: Running an extension entry point directly with node/ts-node during development; calling joinSession() in CI; confusing joinSession() with CopilotClient-based session creation for standalone apps.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/9fce2fdcb9b348d1. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/extension.ts:114

} from "./factory.js";

/**
 * Joins the current foreground session.
 *
 * @param config - Configuration to add to the session
 * @returns A promise that resolves with the joined session
 *
 * @example
 * ```typescript
 * import { joinSession } from "@github/copilot-sdk/extension";
 *
 * const session = await joinSession({ tools: [myTool] });
 * ```
 */
export async function joinSession(config: JoinSessionConfig = {}): Promise<CopilotSession> {
    const sessionId = process.env.SESSION_ID;
    if (!sessionId) {
        throw new Error(
            "joinSession() is intended for extensions running as child processes of the Copilot CLI."
        );
    }

    const client = new CopilotClient({ _internalConnection: { kind: "parent-process" } });

    // Strip `extensionSdkPath` at runtime even though `JoinSessionConfig` omits it
    // at the type level — untyped (JS) callers can still slip it through, and
    // honoring it here would be misleading since the extension subprocess has
    // already been forked by the host with the SDK the host chose.
    const {
        extensionSdkPath: _stripped,
        factories,
        requestedEnvironmentVariables,
        ...rest
    } = config as JoinSessionConfig & {
        extensionSdkPath?: string;
    };

View on GitHub (pinned to cd8cf15dc3)