{"record":{"id":"9bed55910c14474a","repo":"github/copilot-sdk","slug":"copilot-request-was-cancelled-by-the-runtime","errorCode":null,"errorMessage":"Copilot request was cancelled by the runtime.","messagePattern":"Copilot request was cancelled by the runtime\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"nodejs/src/copilotRequestHandler.ts","lineNumber":595,"sourceCode":"    async startResponse(init: ResponseInit): Promise<void> {\n        if (this.#started) {\n            throw new Error(\"Copilot request response start() called twice.\");\n        }\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) {","sourceCodeStart":577,"sourceCodeEnd":613,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/copilotRequestHandler.ts#L577-L613","documentation":"When the runtime cancels the request, the handler marks itself cancelled and further response writes would target a request the runtime no longer reads. writeResponse() checks the cancelled flag and throws before sending an httpResponseChunk over the RPC channel.","triggerScenarios":"The runtime cancelled the request (client disconnect/timeout) and the handler subsequently calls writeResponse() — usually directly, or via streamResponse() — to emit another body chunk.","commonSituations":"Long-running streaming handlers (SSE-style output) where the user aborts mid-stream; loops that keep generating chunks without checking cancellation between iterations.","solutions":["Catch this error in the streaming loop, stop generating, and clean up — it means the peer is gone.","Check the handler's cancellation state before each write and break out of the loop early.","Suppress this error as a normal abort rather than logging it as a failure.","Reduce per-chunk work so cancellation between chunks is detected quickly."],"exampleFix":"// before\nfor (const chunk of chunks) { await h.writeResponse(chunk); }\n// after\ntry {\n  for (const chunk of chunks) {\n    if (h.isCancelled) break;\n    await h.writeResponse(chunk);\n  }\n} catch (e) {\n  if (!/cancelled by the runtime/.test(String(e.message))) throw e;\n}","handlingStrategy":"try-catch","validationCode":"function isCancelled(h): boolean {\n  return (h as { isCancelled?: boolean }).isCancelled === true;\n}\nif (isCancelled(handler)) return; // skip write","typeGuard":null,"tryCatchPattern":"try {\n  await handler.writeResponse(chunk);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('cancelled by the runtime')) { await cleanup(); return; }\n  throw e;\n}","preventionTips":["Check the cancelled flag before every write in streaming loops.","Break out of generation loops promptly on cancellation.","Treat this error as a normal abort, not a failure to alert on.","Minimize per-chunk latency so cancellation is noticed quickly."],"tags":["cancellation","streaming","websocket"],"backgroundTag":"broken-pipe","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"}