github/copilot-sdk · error · RuntimeError
Copilot request response write() called before start().
Error message
Copilot request response write() called before start().
What it means
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.
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.
Example fix
// before
await response.write_response("hello") # never started
// after
await response.start_response(200, "OK", {"content-type": "text/plain"})
await response.write_response("hello") Defensive patterns
Strategy: validation
Validate before calling
if not response.started:
await response.start_response(200, "OK", {"content-type": "text/plain"}) Type guard
def has_started(response) -> bool:
return bool(getattr(response, "started", False)) Try / catch
try:
await response.write_response(data)
except RuntimeError as e:
if "before start()" in str(e):
await response.start_response(200, "OK", {})
await response.write_response(data)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Copilot request response write() called after end()/error().
- Copilot request response start() called twice.
- Copilot request response already finished.
- Copilot request was cancelled by the runtime.
- Invalid entry '*': there is no bare wildcard. Use one or…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/ca635c3e8270aa20.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/copilot_request_handler.py:458
if self.started:
raise RuntimeError("Copilot request response start() called twice.")
if self.finished:
raise RuntimeError("Copilot request response already finished.")
self.started = True
await self._require_rpc().http_response_start(
LlmInferenceHTTPResponseStartRequest(
headers=headers or {},
request_id=self.request_id,
status=status,
status_text=status_text,
)
)
async def write_response(self, data: str | bytes) -> None:
if self.cancelled:
raise RuntimeError("Copilot request was cancelled by the runtime.")
if not self.started:
raise RuntimeError("Copilot request response write() called before start().")
if self.finished:
raise RuntimeError("Copilot request response write() called after end()/error().")
if isinstance(data, bytes):
payload = base64.b64encode(data).decode("ascii")
is_binary = True
else:
payload = data
is_binary = False
await self._require_rpc().http_response_chunk(
LlmInferenceHTTPResponseChunkRequest(
data=payload,
request_id=self.request_id,
binary=is_binary or None,
end=False,
)
)
async def end_response(self) -> None:View on GitHub (pinned to cd8cf15dc3)