github/copilot-sdk · error

Copilot request response start() called twice.

Error message

Copilot request response start() called twice.

What it means

The response emitter is a strict state machine: startResponse() may run exactly once per request, followed by writes and exactly one end/error. A second startResponse() call means the handler tried to begin the HTTP response twice, so the RPC httpResponseStart would be sent twice; the library prevents that by throwing.

Solutions

  1. Use ONE response style per handler: either the high-level helpers (finalize/streamResponse) or the low-level startResponse/writeResponse/endResponse sequence, never both.
  2. Track locally whether the response has started before attempting to start it in shared/retry code paths.
  3. If a fallback response is needed after a failure, use errorResponse() rather than starting a new response.
  4. Review handler code paths (e.g. branches that both finalize and stream) and remove the duplicate start.

Example fix

// before
await handler.startResponse({ status: 200 });
await handler.streamResponse(data); // throws: starts again
// after
await handler.streamResponse(data); // single entry point handles start/write/end
Defensive patterns

Strategy: validation

Validate before calling

let responseStarted = false;
function beginResponse(h, init) {
  if (responseStarted) return;
  responseStarted = true;
  return h.startResponse(init);
}

Try / catch

try {
  await handler.startResponse({ status: 200 });
} catch (e) {
  if (e instanceof Error && e.message.includes('start() called twice')) return; // already started
  throw e;
}

Prevention

When it happens

Trigger: Calling startResponse() explicitly and then also using streamResponse() (or finalize()), both of which invoke startResponse; or invoking startResponse() twice on the same handler instance.

Common situations: Mixing the high-level response helpers (finalize/streamResponse) with low-level start/write/end calls in the same handler; retry/fallback logic that re-sends a response after an earlier path already started it.

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

Appendix: source

Thrown at nodejs/src/copilotRequestHandler.ts:579

                        );
                    }
                    if (item.end) {
                        this.#drained = true;
                        return { value: undefined, done: true };
                    }
                    return { value: item.chunk ?? new Uint8Array(), done: false };
                },
            }),
        };
    }

    // --- Response emit (driven by the handler). Strict state machine: ---
    // startResponse once -> 0..N writeResponse -> exactly one of
    // endResponse / errorResponse.

    async startResponse(init: ResponseInit): Promise<void> {
        if (this.#started) {
            throw new Error("Copilot request response start() called twice.");
        }
        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) {

View on GitHub (pinned to cd8cf15dc3)