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

  1. Check `response.finished` before each write and stop the stream when true.
  2. Ensure end_response()/error() is called only after the write loop completes, and cancel producers before finishing.
  3. 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

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


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:
            return

View on GitHub (pinned to cd8cf15dc3)