{"record":{"id":"d89b939abf9c196b","repo":"github/copilot-sdk","slug":"copilot-request-response-start-called-twice","errorCode":null,"errorMessage":"Copilot request response start() called twice.","messagePattern":"Copilot request response start\\(\\) called twice\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nodejs/src/copilotRequestHandler.ts","lineNumber":579,"sourceCode":"                        );\n                    }\n                    if (item.end) {\n                        this.#drained = true;\n                        return { value: undefined, done: true };\n                    }\n                    return { value: item.chunk ?? new Uint8Array(), done: false };\n                },\n            }),\n        };\n    }\n\n    // --- Response emit (driven by the handler). Strict state machine: ---\n    // startResponse once -> 0..N writeResponse -> exactly one of\n    // endResponse / errorResponse.\n\n    async startResponse(init: ResponseInit): Promise<void> {\n        if (this.#started) {\n            throw new Error(\"Copilot request response start() called twice.\");\n        }\n        if (this.#finished) {\n            throw new Error(\"Copilot request response already finished.\");\n        }\n        this.#started = true;\n        await this.#rpc().llmInference.httpResponseStart({\n            requestId: this.requestId,\n            status: init.status,\n            statusText: init.statusText,\n            headers: init.headers ?? {},\n        });\n    }\n\n    async writeResponse(data: string | Uint8Array): Promise<void> {\n        if (this.#cancelled) {\n            throw new Error(\"Copilot request was cancelled by the runtime.\");\n        }\n        if (!this.#started) {","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/copilotRequestHandler.ts#L561-L597","documentation":"The response emitter is a strict state machine: startResponse() may run exactly once per request, followed by writes and exactly one end/error. A second startResponse() call means the handler tried to begin the HTTP response twice, so the RPC httpResponseStart would be sent twice; the library prevents that by throwing.","triggerScenarios":"Calling startResponse() explicitly and then also using streamResponse() (or finalize()), both of which invoke startResponse; or invoking startResponse() twice on the same handler instance.","commonSituations":"Mixing the high-level response helpers (finalize/streamResponse) with low-level start/write/end calls in the same handler; retry/fallback logic that re-sends a response after an earlier path already started it.","solutions":["Use ONE response style per handler: either the high-level helpers (finalize/streamResponse) or the low-level startResponse/writeResponse/endResponse sequence, never both.","Track locally whether the response has started before attempting to start it in shared/retry code paths.","If a fallback response is needed after a failure, use errorResponse() rather than starting a new response.","Review handler code paths (e.g. branches that both finalize and stream) and remove the duplicate start."],"exampleFix":"// before\nawait handler.startResponse({ status: 200 });\nawait handler.streamResponse(data); // throws: starts again\n// after\nawait handler.streamResponse(data); // single entry point handles start/write/end","handlingStrategy":"validation","validationCode":"let responseStarted = false;\nfunction beginResponse(h, init) {\n  if (responseStarted) return;\n  responseStarted = true;\n  return h.startResponse(init);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await handler.startResponse({ status: 200 });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('start() called twice')) return; // already started\n  throw e;\n}","preventionTips":["Pick one response style per handler: high-level helpers OR low-level start/write/end — never mix.","Return immediately after finishing a response so no further emit code runs.","Centralize response emission in a single helper that guards a started flag.","Avoid re-emit logic in catch/retry paths; use errorResponse() for failures instead."],"tags":["state-machine","response","double-call"],"backgroundTag":"invalid-state-transition","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}