Tencent/WeKnora · error
open zip: %w
Error message
open zip: %w
What it means
After downloading the ZIP bytes, downloadAndExtractZip opens them with zip.NewReader. This error means the downloaded payload is not a valid ZIP archive (bad magic bytes, truncated archive, or an HTML/JSON error page served with 200 OK). The download succeeded but the content is not a ZIP.
Source
Thrown at internal/infrastructure/docparser/mineru_cloud_converter.go:376
}
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")
}
sort.Slice(mdFiles, func(i, j int) bool {
di, dj := strings.Count(mdFiles[i], "/"), strings.Count(mdFiles[j], "/")
if di != dj {
return di < djView on GitHub (pinned to 988cbb0330)
Solutions
- Log the first bytes / size of zipData on failure to see what was actually downloaded (HTML/XML error vs truncated ZIP).
- Retry the download; compare Content-Length with actual byte count to detect truncation.
- Check the download URL is still valid (presigned URLs / result links can expire after the async job completes).
- Ensure the MinerU result ZIP upload itself completed before polling marks the job done.
Example fix
// before
zr, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
if err != nil {
return "", nil, fmt.Errorf("open zip: %w", err)
}
// after
zr, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
if err != nil {
return "", nil, fmt.Errorf("open zip (len=%d, head=%q): %w", len(zipData), zipData[:min(64, len(zipData))], err)
} Defensive patterns
Strategy: validation
Validate before calling
if len(zipData) < 4 || string(zipData[:2]) != "PK" {
return fmt.Errorf("downloaded payload is not a zip (size=%d)", len(zipData))
} Type guard
func isZip(data []byte) bool {
return len(data) >= 4 && data[0] == 'P' && data[1] == 'K' && (data[2] == 3 || data[2] == 5 || data[2] == 7)
} Try / catch
md, _, err := conv.Read(ctx, req)
if err != nil && strings.Contains(err.Error(), "open zip") {
// inspect/log payload, do not retry blindly; re-trigger the parse job
return fmt.Errorf("mineru returned non-zip result: %w", err)
} Prevention
- Validate magic bytes (PK\x03\x04) before calling zip.NewReader
- Confirm result download URLs haven't expired before fetching
- Check gateway/proxy behavior for error pages served with HTTP 200
- Verify job completion status before downloading the artifact
When it happens
Trigger: zip.NewReader(bytes.NewReader(zipData), len(zipData)) fails: MinerU or an intermediary returned a non-ZIP 200 response (e.g. HTML error page from a gateway), or the ZIP was truncated mid-transfer without an io error.
Common situations: Reverse proxy returns a 200 HTML 'gateway timeout'/'maintenance' page instead of the ZIP; MinIO/S3 presigned URL expired and returns XML error with 200 via a misconfigured gateway; partial download after connection close without read error.
Related errors
- read md file: %w
- decode suggestion JSON: %w
- open source file: %w
- read source file: %w
- parse document: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/694010bd536e1b56.
Report an issue: GitHub.