github/copilot-sdk · error · RuntimeError
Copilot request response write() called after end()/error().
Error message
Copilot request response write() called after end()/error().
What it means
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.
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).
Example fix
// before
await response.end_response()
await response.write_response("leftover") # finished
// after
await response.write_response("leftover")
await response.end_response() Defensive patterns
Strategy: validation
Validate before calling
if response.finished:
break # stop the write loop; response already ended Type guard
def is_writable(response) -> bool:
return not response.cancelled and response.started and not response.finished Try / catch
try:
await response.write_response(chunk)
except RuntimeError as e:
if "after end()/error()" in str(e):
break
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
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 before start().
- 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/57d6d12811dd6161.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/copilot_request_handler.py:460
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:
if self.finished:
returnView on GitHub (pinned to cd8cf15dc3)