Tencent/WeKnora · error

read zip body: %w

Error message

read zip body: %w

What it means

downloadAndExtractZip downloads the result ZIP produced by an async MinerU parsing job and reads it fully into memory with io.ReadAll. This error wraps any failure while reading the HTTP response body (network reset mid-transfer, timeout, context cancellation, truncated connection). It does not indicate the ZIP itself is corrupt — only that the raw bytes could not be downloaded.

Source

Thrown at internal/infrastructure/docparser/mineru_cloud_converter.go:371

var imgRefPattern = regexp.MustCompile(`!\[[^\]]*\]\(([^)]+)\)`)

func downloadAndExtractZip(zipURL string) (string, []types.ImageRef, error) {
	if err := utils.ValidateURLForSSRF(zipURL); err != nil {
		return "", nil, fmt.Errorf("zip URL blocked by SSRF check: %v", err)
	}
	client := utils.NewSSRFSafeHTTPClient(utils.SSRFSafeHTTPClientConfig{Timeout: 120 * time.Second, MaxRedirects: 5})
	resp, err := client.Get(zipURL)
	if err != nil {
		return "", nil, fmt.Errorf("download zip: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", nil, fmt.Errorf("download zip status %d", resp.StatusCode)
	}

	zipData, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", nil, fmt.Errorf("read zip body: %w", err)
	}

	zr, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
	if err != nil {
		return "", nil, fmt.Errorf("open zip: %w", err)
	}

	// Find .md files
	var mdFiles []string
	entries := make(map[string]*zip.File)
	for _, f := range zr.File {
		entries[f.Name] = f
		if strings.HasSuffix(f.Name, ".md") {
			mdFiles = append(mdFiles, f.Name)
		}
	}
	if len(mdFiles) == 0 {
		return "", nil, fmt.Errorf("no .md file found in zip")

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Retry downloadAndExtractZip with backoff — transient network failures usually succeed on a second attempt.
  2. Check the caller's ctx deadline; increase it if the ZIP is large relative to the timeout.
  3. Verify any proxy/load balancer between the service and MinerU allows large streaming responses (raise idle/response timeouts, disable response buffering limits).
  4. Check upstream logs for the wrapped cause (%w) to distinguish reset vs timeout vs cancellation.

Example fix

// before
zipData, err := io.ReadAll(resp.Body)
if err != nil {
    return "", nil, fmt.Errorf("read zip body: %w", err)
}
// after
zipData, err := io.ReadAll(resp.Body)
if err != nil {
    if ctx.Err() != nil {
        return "", nil, fmt.Errorf("read zip body: canceled: %w", ctx.Err())
    }
    return "", nil, fmt.Errorf("read zip body: %w", err) // retryable
}
Defensive patterns

Strategy: retry

Try / catch

md, _, err := conv.Read(ctx, req)
if err != nil && strings.Contains(err.Error(), "read zip body") {
    // transient network: retry with backoff, respecting ctx
    md, _, err = conv.Read(ctx, req)
}

Prevention

When it happens

Trigger: The HTTP GET that fetches the finished job's ZIP returns 200 but the response body fails during io.ReadAll(resp.Body): connection reset, unexpected EOF, context deadline exceeded while streaming a large ZIP.

Common situations: Large result ZIPs from big PDFs dropped by proxies/load balancers with idle-timeout limits; caller context canceled because the user request timed out upstream; unstable network between the service and the MinerU result storage endpoint.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/e10cdc6f83230da8. Report an issue: GitHub.