{"record":{"id":"1a66ad3475667b99","repo":"github/copilot-sdk","slug":"llm-inference-request-was-cancelled-by-the-runtime-1a66ad","errorCode":null,"errorMessage":"LLM inference request was cancelled by the runtime.","messagePattern":"LLM inference request was cancelled by the runtime\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"dotnet/src/CopilotRequestHandler.cs","lineNumber":815,"sourceCode":"        {\n            return;\n        }\n\n        _finished = true;\n        await ServerRpc()\n            .LlmInference.HttpResponseChunkAsync(\n                RequestId,\n                string.Empty,\n                end: true,\n                error: new LlmInferenceHttpResponseChunkError { Message = message, Code = code })\n            .ConfigureAwait(false);\n    }\n\n    private async Task WriteChunkAsync(string data, bool binary)\n    {\n        if (_cancelled)\n        {\n            throw new InvalidOperationException(\"LLM inference request was cancelled by the runtime.\");\n        }\n\n        if (!_started)\n        {\n            throw new InvalidOperationException(\"LLM inference response WriteAsync() called before StartAsync().\");\n        }\n\n        if (_finished)\n        {\n            throw new InvalidOperationException(\"LLM inference response WriteAsync() called after EndAsync()/ErrorAsync().\");\n        }\n\n        await ServerRpc()\n            .LlmInference.HttpResponseChunkAsync(RequestId, data, binary: binary, end: false)\n            .ConfigureAwait(false);\n    }\n\n    private ServerRpc ServerRpc() =>","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/dotnet/src/CopilotRequestHandler.cs#L797-L833","documentation":"LlmInferenceExchange.WriteChunkAsync refuses to write body chunks once the runtime has cancelled the request (_cancelled set). This InvalidOperationException prevents writing data for an aborted exchange and preserves protocol ordering (chunks only after StartAsync).","triggerScenarios":"Writing response chunks after cancellation was observed, e.g. continuing a chunk-write loop after an OperationCanceledException was caught, or queuing buffered writes that flush after cancellation; also thrown if WriteAsync is called before StartAsync (different message).","commonSituations":"A producer task keeps streaming into the response after the runtime aborted the request; app buffers chunks and flushes too late; cancellation was swallowed earlier so writes continue silently.","solutions":["Check the cancelled state (and observe OperationCanceledException) and stop writing immediately","Abort/complete the write pipeline upon cancellation instead of continuing to enqueue chunks","Ensure StartAsync is awaited before any WriteAsync call (separate pre-start error)"],"exampleFix":"// before\nforeach (var chunk in chunks) await exchange.WriteAsync(chunk);\n// after\nforeach (var chunk in chunks)\n{\n    if (exchange.IsCancelled) break; // stop writing after cancellation\n    await exchange.WriteAsync(chunk);\n}","handlingStrategy":"type-guard","validationCode":"if (exchange.IsCancelled) return; // do not write after cancellation","typeGuard":"bool CanWrite(LlmInferenceExchange ex) => !ex.IsCancelled && ex.Started;","tryCatchPattern":"try { await exchange.WriteAsync(chunk); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"cancelled by the runtime\")) { /* stop streaming; dispose exchange */ }","preventionTips":["Check cancelled state before each chunk write","Complete the write pipeline on cancellation","Ensure StartAsync precedes WriteAsync","Never swallow cancellation exceptions upstream of writes"],"tags":["llm","cancellation","streaming","dotnet"],"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"}