{"record":{"id":"63d5d5f2da75cbf4","repo":"github/copilot-sdk","slug":"copilot-request-response-write-called-after-end","errorCode":null,"errorMessage":"Copilot request response write() called after end()/error().","messagePattern":"Copilot request response write\\(\\) called after end\\(\\)/error\\(\\)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nodejs/src/copilotRequestHandler.ts","lineNumber":601,"sourceCode":"        }\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) {\n            throw new Error(\"Copilot request response write() called before start().\");\n        }\n        if (this.#finished) {\n            throw new Error(\"Copilot request response write() called after end()/error().\");\n        }\n        const isString = typeof data === \"string\";\n        await this.#rpc().llmInference.httpResponseChunk({\n            requestId: this.requestId,\n            data: isString ? data : Buffer.from(data).toString(\"base64\"),\n            binary: !isString,\n            end: false,\n        });\n    }\n\n    async endResponse(): Promise<void> {\n        if (this.#finished) {\n            return;\n        }\n        this.#finished = true;\n        await this.#rpc().llmInference.httpResponseChunk({\n            requestId: this.requestId,\n            data: \"\",","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/copilotRequestHandler.ts#L583-L619","documentation":"After endResponse() or errorResponse() runs, the response is terminally finished and no further httpResponseChunk writes are valid. writeResponse() checks the finished flag and throws to keep the response lifecycle one start, N writes, one end/error.","triggerScenarios":"Calling writeResponse() after endResponse()/errorResponse() completed — e.g. flushing trailing data after ending, or a loop that keeps writing past a finally block that ends the response.","commonSituations":"Streams whose completion and end-of-response are handled in different places; catch/finally blocks that call endResponse() while the generator loop continues; double-sending a trailer.","solutions":["Stop writing once endResponse()/errorResponse() has been called; reorder so end is the last statement after the write loop.","Use a local finished flag around the emit loop and check it before each write.","Prefer streamResponse()/finalize() which encapsulate ordering and prevent post-end writes.","On catching this error, treat the extra data as dropped and log at debug level."],"exampleFix":"// before\nawait h.endResponse();\nawait h.writeResponse('trailer'); // throws\n// after\nawait h.writeResponse('trailer');\nawait h.endResponse(); // end strictly last","handlingStrategy":"validation","validationCode":"let done = false;\nasync function safeWrite(h, data) {\n  if (done) return; // no writes after end/error\n  await h.writeResponse(data);\n}\nasync function safeEnd(h) { if (!done) { done = true; await h.endResponse(); } }","typeGuard":null,"tryCatchPattern":"try {\n  await handler.writeResponse(trailer);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('called after end()/error()')) return; // drop late data\n  throw e;\n}","preventionTips":["Make endResponse()/errorResponse() the final statement of the handler.","Guard write loops with a local finished flag.","Use streamResponse()/finalize() to encapsulate ordering.","Verify finally blocks don't end the response while a write loop continues."],"tags":["state-machine","response","lifecycle"],"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"}