github/copilot-sdk · error · IOException
LLM inference response writeResponse() called before…
Error message
LLM inference response writeResponse() called before startResponse()
What it means
This IOException is thrown when a response chunk is written before startResponse() has been called on the LlmInferenceExchange. The exchange enforces a strict lifecycle: startResponse() must initialize the response headers/stream before any chunk can be forwarded over RPC. Writing first would produce a malformed response on the protocol level.
Solutions
- Call startResponse() exactly once before the first writeResponseText/writeResponseBinary
- Verify all early-return/conditional paths still call startResponse before any chunk write
- Centralize response writing in one helper that always invokes startResponse first
- If a complete response is already buffered, use the non-streaming response API instead of chunk writes
Example fix
// before
exchange.writeResponseText("hello");
exchange.startResponse();
// after
exchange.startResponse();
exchange.writeResponseText("hello"); Defensive patterns
Strategy: validation
Validate before calling
// track started state in your handler
if (!responseStarted) {
exchange.startResponse();
responseStarted = true;
} Try / catch
try {
exchange.writeResponseText(chunk);
} catch (IOException e) {
if (String.valueOf(e.getMessage()).contains("before startResponse")) {
exchange.startResponse();
exchange.writeResponseText(chunk);
} else throw e;
} Prevention
- Always pair startResponse with the first write in one helper method
- Never conditionally skip startResponse
- Code-review streaming handlers for lifecycle order
- Write a unit test asserting start-before-write for every handler
When it happens
Trigger: Calling writeResponseText or writeResponseBinary (directly or via a streaming writer) without a preceding startResponse() call on the same exchange instance.
Common situations: Refactoring code that constructs a response and moving the write call ahead of startResponse; conditionally calling startResponse (e.g., only when headers are non-empty) but always writing chunks; error paths that skip startResponse then attempt to emit partial output.
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
- LLM inference response writeResponse() called after…
- LLM inference request was cancelled by the runtime
- LLM inference response used after RPC connection closed
- CLI process not started
- CLI child process was unexpectedly started in parent…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/b35c57d64e2fab2e.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/LlmInferenceExchange.java:235
void errorResponse(String message, String code) throws IOException {
synchronized (lock) {
if (finished) {
return;
}
finished = true;
}
var error = new LlmInferenceHttpResponseChunkError(message, code);
var params = new LlmInferenceHttpResponseChunkParams(requestId, "", null, Boolean.TRUE, error);
join(api().httpResponseChunk(params));
}
private void writeChunk(String data, boolean binary) throws IOException {
synchronized (lock) {
if (cancelled) {
throw new IOException("LLM inference request was cancelled by the runtime");
}
if (!started) {
throw new IOException("LLM inference response writeResponse() called before startResponse()");
}
if (finished) {
throw new IOException(
"LLM inference response writeResponse() called after endResponse()/errorResponse()");
}
}
var params = new LlmInferenceHttpResponseChunkParams(requestId, data, binary ? Boolean.TRUE : null,
Boolean.FALSE, null);
join(api().httpResponseChunk(params));
}
private ServerLlmInferenceApi api() throws IOException {
ServerLlmInferenceApi api = rpcSupplier.get();
if (api == null) {
throw new IOException("LLM inference response used after RPC connection closed");
}
return api;
}View on GitHub (pinned to cd8cf15dc3)