{"record":{"id":"ca635c3e8270aa20","repo":"github/copilot-sdk","slug":"copilot-request-response-write-called-before-sta-ca635c","errorCode":null,"errorMessage":"Copilot request response write() called before start().","messagePattern":"Copilot request response write\\(\\) called before start\\(\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/copilot/copilot_request_handler.py","lineNumber":458,"sourceCode":"        if self.started:\n            raise RuntimeError(\"Copilot request response start() called twice.\")\n        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:","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/copilot_request_handler.py#L440-L476","documentation":"write_response() enforces response lifecycle ordering: the response headers must be started via start_response() before any body data can be written. The `started` flag is false when this write arrives, so the library raises instead of emitting an http_response_body RPC without a matching start. This mirrors the ASGI/Wsgi contract that body bytes require a preceding response start.","triggerScenarios":"Calling write_response() on a fresh response object without calling start_response() first; a code path that skips header emission and jumps straight to body streaming.","commonSituations":"Handlers that assume start_response is called implicitly by the framework; refactored code where the start call was moved into an unrelated branch; writing an early error body before headers are sent.","solutions":["Call response.start_response(status, status_text, headers) once before the first write_response().","Guard writes with `if not response.started: await response.start_response(...)`.","Route all streaming through a single helper that performs start-then-write in order."],"exampleFix":"// before\nawait response.write_response(\"hello\")  # never started\n\n// after\nawait response.start_response(200, \"OK\", {\"content-type\": \"text/plain\"})\nawait response.write_response(\"hello\")","handlingStrategy":"validation","validationCode":"if not response.started:\n    await response.start_response(200, \"OK\", {\"content-type\": \"text/plain\"})","typeGuard":"def has_started(response) -> bool:\n    return bool(getattr(response, \"started\", False))","tryCatchPattern":"try:\n    await response.write_response(data)\nexcept RuntimeError as e:\n    if \"before start()\" in str(e):\n        await response.start_response(200, \"OK\", {})\n        await response.write_response(data)\n    else:\n        raise","preventionTips":["Always call start_response before the first write; wrap start+write in a helper.","Never assume a framework adapter started the response for you.","Assert `started` is True in your streaming loop's first iteration during development."],"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"}