henrygd/beszel · warning

log frame size (%d) exceeds maximum (%d)

Error message

log frame size (%d) exceeds maximum (%d)

What it means

decodeDockerLogStream parses the Docker multiplexed log frame format, where each frame is an 8-byte header carrying a 4-byte big-endian length. This error is returned when a single frame's declared length exceeds maxLogFrameSize, which is a memory-exhaustion guard against malicious or corrupt streams claiming gigantic frames.

Source

Thrown at agent/docker.go:939

	var header [headerSize]byte
	totalBytesRead := 0

	for {
		if _, err := io.ReadFull(reader, header[:]); err != nil {
			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
				return nil
			}
			return err
		}

		frameLen := binary.BigEndian.Uint32(header[4:])
		if frameLen == 0 {
			continue
		}

		// Prevent memory exhaustion from excessively large frames
		if frameLen > maxLogFrameSize {
			return fmt.Errorf("log frame size (%d) exceeds maximum (%d)", frameLen, maxLogFrameSize)
		}

		// Check if reading this frame would exceed total log size limit
		if totalBytesRead+int(frameLen) > maxTotalLogSize {
			// Read and discard remaining data to avoid blocking
			_, _ = io.CopyN(io.Discard, reader, int64(frameLen))
			slog.Debug("Truncating logs: limit reached", "read", totalBytesRead, "limit", maxTotalLogSize)
			return nil
		}

		n, err := io.CopyN(builder, reader, int64(frameLen))
		if err != nil {
			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
				return nil
			}
			return err
		}
		totalBytesRead += int(n)

View on GitHub (pinned to b38fb7dafa)

Solutions

  1. Verify the stream Content-Type to correctly detect multiplexed vs raw streams before decoding
  2. Check that the endpoint is a genuine Docker/Podman socket, not an intermediary rewriting bytes
  3. If legitimately huge log lines are expected, raise maxLogFrameSize deliberately
  4. Retry the log fetch; a single corrupt frame is usually transient

Example fix

// before: blindly decoding whatever arrives
logs, err := getLogs(id, q)
// after: only treat as multiplexed when content type says so (getLogs already does)
contentType := resp.Header.Get("Content-Type")
if !strings.HasSuffix(contentType, "multiplexed-stream") {
    // decode as raw stream; avoids misread frame headers
}
Defensive patterns

Strategy: validation

Validate before calling

// only decode as multiplexed frames when the content type says so
multiplexed := strings.HasSuffix(resp.Header.Get("Content-Type"), "multiplexed-stream")
if !multiplexed { /* decode raw stream instead */ }

Try / catch

if err := decodeDockerLogStream(reader, &builder); err != nil {
    if strings.Contains(err.Error(), "exceeds maximum") {
        log.Warn("corrupt log frame, truncating logs")
        return builder.String(), nil
    }
    return err
}

Prevention

When it happens

Trigger: A log frame header declares a length larger than maxLogFrameSize — a corrupt/malformed stream, a non-multiplexed raw stream misinterpreted as frames, or a hostile daemon/socket feeding oversized length fields.

Common situations: A raw (non-multiplexed) TTY stream whose text bytes are misread as a frame header with an enormous length; middleboxes/proxies mangling the stream; connecting to something that is not a real Docker socket on the configured endpoint.

Related errors


AI-assisted analysis of henrygd/beszel@b38fb7dafa (2026-08-31). Data as JSON: /api/errors/586339b9251e68a4. Report an issue: GitHub.