{"record":{"id":"848cffdb3e06da51","repo":"iOfficeAI/OfficeCLI","slug":"body-read-timed-out","errorCode":null,"errorMessage":"body read timed out","messagePattern":"body read timed out","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/Watch/WatchServer.cs","lineNumber":2242,"sourceCode":"        if (bodyPrefix.Length >= contentLength) return Encoding.UTF8.GetString(bodyPrefix, 0, contentLength);\n\n        var body = new byte[contentLength];\n        bodyPrefix.CopyTo(body, 0);\n        int have = bodyPrefix.Length;\n        using var readCts = CancellationTokenSource.CreateLinkedTokenSource(token);\n        readCts.CancelAfter(PostBodyReadTimeout);\n        try\n        {\n            while (have < contentLength)\n            {\n                var n = await stream.ReadAsync(body.AsMemory(have, contentLength - have), readCts.Token);\n                if (n == 0) break;\n                have += n;\n            }\n        }\n        catch (OperationCanceledException) when (!token.IsCancellationRequested)\n        {\n            throw new InvalidDataException(\"body read timed out\");\n        }\n        return Encoding.UTF8.GetString(body, 0, have);\n    }\n\n    private async Task HandlePostSelectionAsync(NetworkStream stream, Dictionary<string, string> headers, byte[] bodyPrefix, CancellationToken token)\n    {\n        int statusCode = 204;\n        string statusText = \"No Content\";\n\n        try\n        {\n            var body = await ReadPostBodyAsync(stream, headers, bodyPrefix, token);\n\n            // Expected JSON: {\"paths\": [\"/slide[1]/shape[2]\", ...]}\n            var req = JsonSerializer.Deserialize(body, WatchSelectionJsonContext.Default.SelectionRequest);\n            var rawSelection = req?.Paths ?? new List<string>();\n            // BUG-TESTER-R501/R502 + BUG-FUZZER-R5-04: bring selection path\n            // hardening up to parity with mark (Round 2/3 fixes). Each path is","sourceCodeStart":2224,"sourceCodeEnd":2260,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/Watch/WatchServer.cs#L2224-L2260","documentation":"Thrown by ReadPostBodyAsync when reading the declared Content-Length bytes does not complete within PostBodyReadTimeout (a linked CancellationToken cancels and, when the external token is not the cause, converts OperationCanceledException into InvalidDataException 'body read timed out'). It stops a slow client from holding a connection open (slow-loris) after sending a large Content-Length.","triggerScenarios":"A client declares a large Content-Length but sends bytes slowly or stalls mid-body so the read loop cannot finish within PostBodyReadTimeout; a dropped connection mid-upload.","commonSituations":"Flaky/slow network to the watch client; a client that crashes after sending headers; a deliberate slow-loris probe.","solutions":["Send the entire body promptly after headers.","Reduce payload size so it transmits within the timeout.","Reconnect and retry the POST if the connection dropped."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Client side: write the full body in one shot and ensure it matches Content-Length\nvar bytes = System.Text.Encoding.UTF8.GetBytes(bodyJson);\nrequest.ContentLength = bytes.Length; // must equal actual length\nawait stream.WriteAsync(bytes);","typeGuard":null,"tryCatchPattern":"// On the client, treat a timed-out POST as retryable:\nfor (int i = 0; i < 2; i++)\n    try { await PostSelectionAsync(body); return; }\n    catch (System.IO.InvalidDataException) when (i < 1) { /* reconnect and retry */ }","preventionTips":["Flush the whole body immediately after headers.","Keep payloads small enough to fit the read timeout.","Reconnect on a stalled write rather than leaving it half-sent."],"tags":["watch","http","timeout","security","fuzzer"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}