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

  1. Call response.start_response(status, status_text, headers) once before the first write_response().
  2. Guard writes with `if not response.started: await response.start_response(...)`.
  3. 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

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


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)