{"record":{"id":"b35c57d64e2fab2e","repo":"github/copilot-sdk","slug":"llm-inference-response-writeresponse-called-befo","errorCode":null,"errorMessage":"LLM inference response writeResponse() called before startResponse()","messagePattern":"LLM inference response writeResponse\\(\\) called before startResponse\\(\\)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/LlmInferenceExchange.java","lineNumber":235,"sourceCode":"    void errorResponse(String message, String code) throws IOException {\n        synchronized (lock) {\n            if (finished) {\n                return;\n            }\n            finished = true;\n        }\n        var error = new LlmInferenceHttpResponseChunkError(message, code);\n        var params = new LlmInferenceHttpResponseChunkParams(requestId, \"\", null, Boolean.TRUE, error);\n        join(api().httpResponseChunk(params));\n    }\n\n    private void writeChunk(String data, boolean binary) throws IOException {\n        synchronized (lock) {\n            if (cancelled) {\n                throw new IOException(\"LLM inference request was cancelled by the runtime\");\n            }\n            if (!started) {\n                throw new IOException(\"LLM inference response writeResponse() called before startResponse()\");\n            }\n            if (finished) {\n                throw new IOException(\n                        \"LLM inference response writeResponse() called after endResponse()/errorResponse()\");\n            }\n        }\n        var params = new LlmInferenceHttpResponseChunkParams(requestId, data, binary ? Boolean.TRUE : null,\n                Boolean.FALSE, null);\n        join(api().httpResponseChunk(params));\n    }\n\n    private ServerLlmInferenceApi api() throws IOException {\n        ServerLlmInferenceApi api = rpcSupplier.get();\n        if (api == null) {\n            throw new IOException(\"LLM inference response used after RPC connection closed\");\n        }\n        return api;\n    }","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/LlmInferenceExchange.java#L217-L253","documentation":"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.","triggerScenarios":"Calling writeResponseText or writeResponseBinary (directly or via a streaming writer) without a preceding startResponse() call on the same exchange instance.","commonSituations":"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.","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"],"exampleFix":"// before\nexchange.writeResponseText(\"hello\");\nexchange.startResponse();\n// after\nexchange.startResponse();\nexchange.writeResponseText(\"hello\");","handlingStrategy":"validation","validationCode":"// track started state in your handler\nif (!responseStarted) {\n    exchange.startResponse();\n    responseStarted = true;\n}","typeGuard":null,"tryCatchPattern":"try {\n    exchange.writeResponseText(chunk);\n} catch (IOException e) {\n    if (String.valueOf(e.getMessage()).contains(\"before startResponse\")) {\n        exchange.startResponse();\n        exchange.writeResponseText(chunk);\n    } else throw e;\n}","preventionTips":["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"],"tags":["io","lifecycle","api-misuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}