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
- For standalone apps, create a session explicitly with CopilotClient instead of joinSession().
- Run the extension through the Copilot CLI so it is spawned as a child process with SESSION_ID set.
- In development, launch the extension via the CLI extension mechanism rather than invoking the file directly.
- 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
- Call joinSession() only from processes spawned by the Copilot CLI.
- Check SESSION_ID before calling and fall back to CopilotClient for standalone runs.
- Use CopilotClient.createSession() for apps and CI, not joinSession().
- Document that extensions must be launched via the CLI extension mechanism.
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
- Copilot CLI not found at
- Cannot connect: no process for stdio and no host:port for…
- CLI process exited unexpectedly.
- Timeout waiting for CLI to announce port
- CLI process exited unexpectedly.
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)