encode/httpx · error · DecodingError

Zstandard data is incomplete

Error message

Zstandard data is incomplete

What it means

DecodingError('Zstandard data is incomplete') raised in ZStandardDecoder.flush when, after all chunks were decoded, self.decompressor.eof is still False. This means the zstd stream did not reach its end-of-frame marker — the body was truncated before the final frame boundary. The flush() itself is a no-op for zstandard; this is purely an integrity check at stream close.

Source

Thrown at httpx/_decoders.py:199

        assert zstandard is not None
        self.seen_data = True
        output = io.BytesIO()
        try:
            output.write(self.decompressor.decompress(data))
            while self.decompressor.eof and self.decompressor.unused_data:
                unused_data = self.decompressor.unused_data
                self.decompressor = zstandard.ZstdDecompressor().decompressobj()
                output.write(self.decompressor.decompress(unused_data))
        except zstandard.ZstdError as exc:
            raise DecodingError(str(exc)) from exc
        return output.getvalue()

    def flush(self) -> bytes:
        if not self.seen_data:
            return b""
        ret = self.decompressor.flush()  # note: this is a no-op
        if not self.decompressor.eof:
            raise DecodingError("Zstandard data is incomplete")  # pragma: no cover
        return bytes(ret)


class MultiDecoder(ContentDecoder):
    """
    Handle the case where multiple encodings have been applied.
    """

    def __init__(self, children: typing.Sequence[ContentDecoder]) -> None:
        """
        'children' should be a sequence of decoders in the order in which
        each was applied.
        """
        # Note that we reverse the order for decoding.
        self.children = list(reversed(children))

    def decode(self, data: bytes) -> bytes:
        for child in self.children:

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Retry the request — truncation is often transient.
  2. Increase client/server body-size limits and timeouts if the body legitimately exceeds them.
  3. Opt out of zstd: headers={'Accept-Encoding': 'gzip, deflate'} to avoid the end-of-frame integrity check.
  4. Inspect Content-Length vs num_bytes_downloaded to confirm and quantify truncation.

Example fix

// before
resp = client.get(url)  # zstd body truncated
body = resp.content  # DecodingError 'Zstandard data is incomplete'
// after
resp = client.get(url, headers={'Accept-Encoding': 'gzip, deflate'})
body = resp.content
Defensive patterns

Strategy: retry

Validate before calling

# Before reading, sanity-check Content-Length if available
expected = resp.headers.get('Content-Length')
if expected is not None and resp.num_bytes_downloaded > int(expected):
    # likely truncation; avoid the flush() integrity check
    resp.close()

Try / catch

try:
    body = resp.content
except httpx.DecodingError as exc:
    if 'incomplete' in str(exc).lower():
        # truncated zstd; retry the request
        resp = client.get(resp.request.url)
        body = resp.content
    else:
        raise

Prevention

When it happens

Trigger: Response body cut short before the zstd frame terminator; streaming endpoint that closes the connection mid-frame; proxy/load-balancer truncation; chunked-transfer body that ended one chunk too early.

Common situations: Connection drops mid-zstd-body; reverse proxy with a body size limit that cuts the stream; HTTP/2 stream reset before the final frame; server flushing a partial zstd buffer on timeout.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/d01a2e427ad7a309.json. Report an issue: GitHub.