{"record":{"id":"57d6d12811dd6161","repo":"github/copilot-sdk","slug":"copilot-request-response-write-called-after-end-57d6d1","errorCode":null,"errorMessage":"Copilot request response write() called after end()/error().","messagePattern":"Copilot request response write\\(\\) called after end\\(\\)/error\\(\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/copilot/copilot_request_handler.py","lineNumber":460,"sourceCode":"        if self.finished:\n            raise RuntimeError(\"Copilot request response already finished.\")\n        self.started = True\n        await self._require_rpc().http_response_start(\n            LlmInferenceHTTPResponseStartRequest(\n                headers=headers or {},\n                request_id=self.request_id,\n                status=status,\n                status_text=status_text,\n            )\n        )\n\n    async def write_response(self, data: str | bytes) -> None:\n        if self.cancelled:\n            raise RuntimeError(\"Copilot request was cancelled by the runtime.\")\n        if not self.started:\n            raise RuntimeError(\"Copilot request response write() called before start().\")\n        if self.finished:\n            raise RuntimeError(\"Copilot request response write() called after end()/error().\")\n        if isinstance(data, bytes):\n            payload = base64.b64encode(data).decode(\"ascii\")\n            is_binary = True\n        else:\n            payload = data\n            is_binary = False\n        await self._require_rpc().http_response_chunk(\n            LlmInferenceHTTPResponseChunkRequest(\n                data=payload,\n                request_id=self.request_id,\n                binary=is_binary or None,\n                end=False,\n            )\n        )\n\n    async def end_response(self) -> None:\n        if self.finished:\n            return","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/copilot_request_handler.py#L442-L478","documentation":"write_response() rejects writes once the response has been terminated by end_response() or error(); `finished` is a terminal flag. Writing after finish would send body bytes after the response-completion RPC, corrupting the HTTP framing on the runtime channel.","triggerScenarios":"Calling write_response() after end_response() or error() on the same object; a streaming loop that does not break when the finish call happens (e.g. flush-then-write ordering bugs); finalization code finishing the response while a producer coroutine still writes.","commonSituations":"Race between a producer task and a timeout/cleanup task that ends the response; double-send patterns where error() is called then the normal stream continues; retry logic re-entering the write loop after completion.","solutions":["Check `response.finished` before each write and stop the stream when true.","Ensure end_response()/error() is called only after the write loop completes, and cancel producers before finishing.","Structure streaming as: start -> write... -> end, with no writes after the end call (assert in debug builds)."],"exampleFix":"// before\nawait response.end_response()\nawait response.write_response(\"leftover\")  # finished\n\n// after\nawait response.write_response(\"leftover\")\nawait response.end_response()","handlingStrategy":"validation","validationCode":"if response.finished:\n    break  # stop the write loop; response already ended","typeGuard":"def is_writable(response) -> bool:\n    return not response.cancelled and response.started and not response.finished","tryCatchPattern":"try:\n    await response.write_response(chunk)\nexcept RuntimeError as e:\n    if \"after end()/error()\" in str(e):\n        break\n    raise","preventionTips":["Call end_response()/error() only after the write loop exits; cancel producers first.","Check `finished` at the top of each streaming iteration.","Avoid racing a timeout task that ends the response against an active producer."],"tags":["state-machine","http-response","streaming","python"],"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-15T23:17:13.987Z"}