iOfficeAI/OfficeCLI · error · InvalidDataException

body read timed out

Error message

body read timed out

What it means

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.

Source

Thrown at src/officecli/Core/Watch/WatchServer.cs:2242

        if (bodyPrefix.Length >= contentLength) return Encoding.UTF8.GetString(bodyPrefix, 0, contentLength);

        var body = new byte[contentLength];
        bodyPrefix.CopyTo(body, 0);
        int have = bodyPrefix.Length;
        using var readCts = CancellationTokenSource.CreateLinkedTokenSource(token);
        readCts.CancelAfter(PostBodyReadTimeout);
        try
        {
            while (have < contentLength)
            {
                var n = await stream.ReadAsync(body.AsMemory(have, contentLength - have), readCts.Token);
                if (n == 0) break;
                have += n;
            }
        }
        catch (OperationCanceledException) when (!token.IsCancellationRequested)
        {
            throw new InvalidDataException("body read timed out");
        }
        return Encoding.UTF8.GetString(body, 0, have);
    }

    private async Task HandlePostSelectionAsync(NetworkStream stream, Dictionary<string, string> headers, byte[] bodyPrefix, CancellationToken token)
    {
        int statusCode = 204;
        string statusText = "No Content";

        try
        {
            var body = await ReadPostBodyAsync(stream, headers, bodyPrefix, token);

            // Expected JSON: {"paths": ["/slide[1]/shape[2]", ...]}
            var req = JsonSerializer.Deserialize(body, WatchSelectionJsonContext.Default.SelectionRequest);
            var rawSelection = req?.Paths ?? new List<string>();
            // BUG-TESTER-R501/R502 + BUG-FUZZER-R5-04: bring selection path
            // hardening up to parity with mark (Round 2/3 fixes). Each path is

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Send the entire body promptly after headers.
  2. Reduce payload size so it transmits within the timeout.
  3. Reconnect and retry the POST if the connection dropped.
Defensive patterns

Strategy: retry

Validate before calling

// Client side: write the full body in one shot and ensure it matches Content-Length
var bytes = System.Text.Encoding.UTF8.GetBytes(bodyJson);
request.ContentLength = bytes.Length; // must equal actual length
await stream.WriteAsync(bytes);

Try / catch

// On the client, treat a timed-out POST as retryable:
for (int i = 0; i < 2; i++)
    try { await PostSelectionAsync(body); return; }
    catch (System.IO.InvalidDataException) when (i < 1) { /* reconnect and retry */ }

Prevention

When it happens

Trigger: 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.

Common situations: Flaky/slow network to the watch client; a client that crashes after sending headers; a deliberate slow-loris probe.

Understand the failure class

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/848cffdb3e06da51. Report an issue: GitHub.