github/copilot-sdk · error

Copilot request response write() called before start().

Error message

Copilot request response write() called before start().

What it means

The response state machine requires startResponse() to run before any writeResponse(). Writes map to httpResponseChunk RPC calls which only exist after httpResponseStart established status/headers. Writing before start is an out-of-order lifecycle call, so the library throws.

Solutions

  1. Always call await handler.startResponse({ status, headers }) once before the first writeResponse().
  2. Prefer streamResponse() or finalize(), which manage the start/write/end sequence for you.
  3. Audit error paths that might skip startResponse while later code still writes.
  4. Check for swallowed rejections from startResponse that leave #started false while the write path proceeds.

Example fix

// before
await handler.writeResponse('hello'); // throws: not started
// after
await handler.startResponse({ status: 200 });
await handler.writeResponse('hello');
Defensive patterns

Strategy: validation

Validate before calling

let started = false;
async function safeWrite(h, data) {
  if (!started) { await h.startResponse({ status: 200 }); started = true; }
  await h.writeResponse(data);
}

Try / catch

try {
  await handler.writeResponse(chunk);
} catch (e) {
  if (e instanceof Error && e.message.includes('called before start()')) {
    await handler.startResponse({ status: 200 });
    await handler.writeResponse(chunk);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling writeResponse() (or streamResponse internals) without a prior successful startResponse() on the same handler — e.g. skipping start when writing the first chunk manually.

Common situations: Handler authors who assume writeResponse implicitly starts the response; refactorings that removed the start call; conditional code paths where startResponse was skipped due to an earlier error.

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/7bc62d218fe179e1. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/copilotRequestHandler.ts:598

        }
        if (this.#finished) {
            throw new Error("Copilot request response already finished.");
        }
        this.#started = true;
        await this.#rpc().llmInference.httpResponseStart({
            requestId: this.requestId,
            status: init.status,
            statusText: init.statusText,
            headers: init.headers ?? {},
        });
    }

    async writeResponse(data: string | Uint8Array): Promise<void> {
        if (this.#cancelled) {
            throw new Error("Copilot request was cancelled by the runtime.");
        }
        if (!this.#started) {
            throw new Error("Copilot request response write() called before start().");
        }
        if (this.#finished) {
            throw new Error("Copilot request response write() called after end()/error().");
        }
        const isString = typeof data === "string";
        await this.#rpc().llmInference.httpResponseChunk({
            requestId: this.requestId,
            data: isString ? data : Buffer.from(data).toString("base64"),
            binary: !isString,
            end: false,
        });
    }

    async endResponse(): Promise<void> {
        if (this.#finished) {
            return;
        }
        this.#finished = true;

View on GitHub (pinned to cd8cf15dc3)