sipeed/picoclaw · warning

media too large

Error message

media too large

What it means

Inbound media downloaded from the WeCom CDN exceeded wecomOutboundMediaMaxBytes = 20 MiB (media.go:28,301-302). The body is read through io.LimitReader(max+1); reading exactly max+1 bytes proves the object is over the cap and the download is aborted before decrypt/store. Note the per-type send limits are tighter (image/voice 2 MiB, video 10 MiB) but the inbound receive cap is a flat 20 MiB.

Source

Thrown at pkg/channels/wecom/media.go:302

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, resourceURL, nil)
	if err != nil {
		return "", fmt.Errorf("create request: %w", err)
	}
	resp, err := c.mediaClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("download media: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("download media returned HTTP %d", resp.StatusCode)
	}

	data, err := io.ReadAll(io.LimitReader(resp.Body, wecomOutboundMediaMaxBytes+1))
	if err != nil {
		return "", fmt.Errorf("read media: %w", err)
	}
	if len(data) > wecomOutboundMediaMaxBytes {
		return "", fmt.Errorf("media too large")
	}

	if aesKey != "" {
		key, keyErr := decodeMediaAESKey(aesKey)
		if keyErr != nil {
			return "", keyErr
		}
		data, err = decryptAESCBC(key, data)
		if err != nil {
			return "", fmt.Errorf("decrypt media: %w", err)
		}
	}

	filename, contentType := detectWeComMediaMetadata(
		data,
		msgID+fallbackExt,
		resp.Header.Get("Content-Type"),
		resourceURL,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Tell the sender the attachment exceeds 20 MiB and ask for a compressed version or a link
  2. Pre-check size before the full download: HEAD/Content-Length or an early-abort Range read, reject > 20 MiB without transferring
  3. If you self-host and accept the upstream implications, fork and raise wecomOutboundMediaMaxBytes - but WeCom's own upload path tops out at 100 chunks x 512 KiB (~50 MiB)
  4. Convert inbound oversized files into links at the source (upload to a file service, send the URL)

Example fix

// before: full download then size check
data, err := io.ReadAll(io.LimitReader(resp.Body, max+1))
if len(data) > max { return "", fmt.Errorf("media too large") }

// after: reject early from Content-Length when present
if n, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); err == nil && n > int64(max) {
    return "", fmt.Errorf("media too large")
}
Defensive patterns

Strategy: validation

Validate before calling

// before triggering an inbound fetch, probe the CDN object size
func sizeWithinLimit(ctx context.Context, rawurl string, max int64) bool {
    req, err := http.NewRequestWithContext(ctx, http.MethodHead, rawurl, nil)
    if err != nil {
        return true // cannot pre-check; runtime check still applies
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return true
    }
    defer resp.Body.Close()
    return resp.ContentLength < 0 || resp.ContentLength <= max
}

Prevention

When it happens

Trigger: A WeCom chat message carries a file or video whose CDN object is larger than 20 MiB (or whose encrypted ciphertext is still > 20 MiB before decryption); len(data) == 20 MiB+1 after the limited read.

Common situations: Users forwarding large videos, HD screen recordings, or log archives through WeCom; encrypted attachments whose ciphertext slightly exceeds the plaintext size; senders on unlimited plans unaware of the receiver cap.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/0de975c4d8997729. Report an issue: GitHub.