github/copilot-sdk · error

Copilot request response write() called after end()/error().

Error message

Copilot request response write() called after end()/error().

What it means

After endResponse() or errorResponse() runs, the response is terminally finished and no further httpResponseChunk writes are valid. writeResponse() checks the finished flag and throws to keep the response lifecycle one start, N writes, one end/error.

Solutions

  1. Stop writing once endResponse()/errorResponse() has been called; reorder so end is the last statement after the write loop.
  2. Use a local finished flag around the emit loop and check it before each write.
  3. Prefer streamResponse()/finalize() which encapsulate ordering and prevent post-end writes.
  4. On catching this error, treat the extra data as dropped and log at debug level.

Example fix

// before
await h.endResponse();
await h.writeResponse('trailer'); // throws
// after
await h.writeResponse('trailer');
await h.endResponse(); // end strictly last
Defensive patterns

Strategy: validation

Validate before calling

let done = false;
async function safeWrite(h, data) {
  if (done) return; // no writes after end/error
  await h.writeResponse(data);
}
async function safeEnd(h) { if (!done) { done = true; await h.endResponse(); } }

Try / catch

try {
  await handler.writeResponse(trailer);
} catch (e) {
  if (e instanceof Error && e.message.includes('called after end()/error()')) return; // drop late data
  throw e;
}

Prevention

When it happens

Trigger: Calling writeResponse() after endResponse()/errorResponse() completed — e.g. flushing trailing data after ending, or a loop that keeps writing past a finally block that ends the response.

Common situations: Streams whose completion and end-of-response are handled in different places; catch/finally blocks that call endResponse() while the generator loop continues; double-sending a trailer.

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

Appendix: source

Thrown at nodejs/src/copilotRequestHandler.ts:601

        }
        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;
        await this.#rpc().llmInference.httpResponseChunk({
            requestId: this.requestId,
            data: "",

View on GitHub (pinned to cd8cf15dc3)