{"record":{"id":"bc4d087647779c90","repo":"github/copilot-sdk","slug":"websocket-response-bridge-is-not-attached","errorCode":null,"errorMessage":"WebSocket response bridge is not attached","messagePattern":"WebSocket response bridge is not attached","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nodejs/src/copilotRequestHandler.ts","lineNumber":85,"sourceCode":" * forwarding traffic to the real upstream, subclass {@link CopilotWebSocketForwarder}\n * instead, which connects upstream and forwards by default.\n *\n * @experimental\n */\nexport abstract class CopilotWebSocketHandler implements AsyncDisposable {\n    readonly #response: CopilotWebSocketResponseBridge;\n    readonly #completion: Promise<CopilotWebSocketCloseStatus>;\n    #resolveCompletion!: (status: CopilotWebSocketCloseStatus) => void;\n    #closed = false;\n    [kSuppressCloseOnDispose] = false;\n\n    protected readonly context: CopilotRequestContext;\n\n    protected constructor(context: CopilotRequestContext) {\n        this.context = context;\n        const bridge = (context as Partial<InternalContext>)[kBridge];\n        if (!bridge) {\n            throw new Error(\"WebSocket response bridge is not attached\");\n        }\n        this.#response = bridge;\n        this.#completion = new Promise<CopilotWebSocketCloseStatus>((resolve) => {\n            this.#resolveCompletion = resolve;\n        });\n    }\n\n    async sendResponseMessage(data: string | Uint8Array): Promise<void> {\n        await this.#response.write(data);\n    }\n\n    async close(\n        status: CopilotWebSocketCloseStatus = CopilotWebSocketCloseStatus.normalClosure\n    ): Promise<void> {\n        if (this.#closed) {\n            return;\n        }\n        this.#closed = true;","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/copilotRequestHandler.ts#L67-L103","documentation":"CopilotRequestHandler's protected constructor requires a WebSocket response bridge to have been attached to the CopilotRequestContext under the internal kBridge symbol. The bridge is what carries responses back to the runtime over the WebSocket RPC channel. If it is missing, the handler cannot emit responses, so construction fails immediately.","triggerScenarios":"Calling the protected CopilotRequestHandler constructor (directly or from a subclass via super(context)) with a context object created outside the runtime's request-dispatch path, so no bridge was attached under kBridge.","commonSituations":"Subclassing CopilotRequestHandler and instantiating it manually in tests or scripts with a plain/partial context instead of receiving the runtime-provided context; using an older/newer SDK version where the internal bridge key or attachment mechanism changed.","solutions":["Do not construct request handlers yourself; let the Copilot runtime create them and only implement the handler entry points.","If subclassing, pass through the exact context object given to your handler factory/callback, never a hand-built context object.","In tests, use the SDK's provided test harness/factory that attaches the internal bridge rather than constructing the handler directly.","Align SDK versions so the runtime attaching the bridge and the handler reading kBridge are the same package version."],"exampleFix":"// before\nconst ctx = { requestId: 'r1' } as CopilotRequestContext;\nconst handler = new MyHandler(ctx); // throws: no bridge\n// after\nexport default createRequestHandler((ctx) => new MyHandler(ctx)); // runtime-attached context","handlingStrategy":"validation","validationCode":"function canCreateHandler(ctx: unknown): ctx is CopilotRequestContext {\n  return !!ctx && typeof ctx === 'object' && !!(ctx as Partial<InternalContext>)[kBridge];\n}\nif (!canCreateHandler(ctx)) throw new Error('context lacks WebSocket response bridge');","typeGuard":"const isAttachableContext = (c: unknown): c is CopilotRequestContext & { [kBridge]?: unknown } =>\n  typeof c === 'object' && c !== null;","tryCatchPattern":"try {\n  const handler = new MyHandler(ctx);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('bridge is not attached')) {\n    // context not runtime-provided; fix construction site\n  }\n  throw e;\n}","preventionTips":["Never construct request handlers manually; use the runtime's handler factory/registration API.","Pass through the runtime-provided context object unchanged to super().","In tests, use the SDK harness that attaches the internal bridge.","Keep SDK package versions aligned between runtime and extension."],"tags":["websocket","initialization","internal-api"],"backgroundTag":"internal-invariant-violation","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}