{"record":{"id":"7bc62d218fe179e1","repo":"github/copilot-sdk","slug":"copilot-request-response-write-called-before-sta","errorCode":null,"errorMessage":"Copilot request response write() called before start().","messagePattern":"Copilot request response write\\(\\) called before start\\(\\)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nodejs/src/copilotRequestHandler.ts","lineNumber":598,"sourceCode":"        }\n        if (this.#finished) {\n            throw new Error(\"Copilot request response already finished.\");\n        }\n        this.#started = true;\n        await this.#rpc().llmInference.httpResponseStart({\n            requestId: this.requestId,\n            status: init.status,\n            statusText: init.statusText,\n            headers: init.headers ?? {},\n        });\n    }\n\n    async writeResponse(data: string | Uint8Array): Promise<void> {\n        if (this.#cancelled) {\n            throw new Error(\"Copilot request was cancelled by the runtime.\");\n        }\n        if (!this.#started) {\n            throw new Error(\"Copilot request response write() called before start().\");\n        }\n        if (this.#finished) {\n            throw new Error(\"Copilot request response write() called after end()/error().\");\n        }\n        const isString = typeof data === \"string\";\n        await this.#rpc().llmInference.httpResponseChunk({\n            requestId: this.requestId,\n            data: isString ? data : Buffer.from(data).toString(\"base64\"),\n            binary: !isString,\n            end: false,\n        });\n    }\n\n    async endResponse(): Promise<void> {\n        if (this.#finished) {\n            return;\n        }\n        this.#finished = true;","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/copilotRequestHandler.ts#L580-L616","documentation":"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.","triggerScenarios":"Calling writeResponse() (or streamResponse internals) without a prior successful startResponse() on the same handler — e.g. skipping start when writing the first chunk manually.","commonSituations":"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.","solutions":["Always call await handler.startResponse({ status, headers }) once before the first writeResponse().","Prefer streamResponse() or finalize(), which manage the start/write/end sequence for you.","Audit error paths that might skip startResponse while later code still writes.","Check for swallowed rejections from startResponse that leave #started false while the write path proceeds."],"exampleFix":"// before\nawait handler.writeResponse('hello'); // throws: not started\n// after\nawait handler.startResponse({ status: 200 });\nawait handler.writeResponse('hello');","handlingStrategy":"validation","validationCode":"let started = false;\nasync function safeWrite(h, data) {\n  if (!started) { await h.startResponse({ status: 200 }); started = true; }\n  await h.writeResponse(data);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await handler.writeResponse(chunk);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('called before start()')) {\n    await handler.startResponse({ status: 200 });\n    await handler.writeResponse(chunk);\n    return;\n  }\n  throw e;\n}","preventionTips":["Always call startResponse() before the first writeResponse().","Prefer streamResponse()/finalize() to avoid manual ordering.","Audit paths that skip startResponse conditionally.","Don't ignore startResponse rejections — a swallowed failure leaves the state machine unstarted."],"tags":["state-machine","response","ordering"],"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-16T04:17:20.429Z"}