multica-ai/multica · error

zip reader: %w

Error message

zip reader: %w

What it means

Returned by extractBinaryFromZip when archive/zip.NewReader rejects the buffered bytes as a valid zip archive. The update downloader fully buffers the release artifact in memory and then asks the zip package to parse its central directory; any corruption, truncation, or non-zip payload (e.g. an HTML error page from a proxy) surfaces here. The underlying error is usually zip.ErrFormat ('not a valid zip file') or a bad central-directory offset.

Source

Thrown at server/internal/cli/update.go:521

				return nil, fmt.Errorf("read binary: %w", err)
			}
			return data, nil
		}
	}
}

// extractBinaryFromZip reads a .zip stream and returns the contents of the
// named file entry. The zip format requires random access, so the full archive
// is buffered in memory.
func extractBinaryFromZip(r io.Reader, name string) ([]byte, error) {
	buf, err := io.ReadAll(r)
	if err != nil {
		return nil, fmt.Errorf("read zip data: %w", err)
	}

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

	for _, f := range zr.File {
		if filepath.Base(f.Name) == name && !f.FileInfo().IsDir() {
			rc, err := f.Open()
			if err != nil {
				return nil, fmt.Errorf("open zip entry: %w", err)
			}
			defer rc.Close()

			data, err := io.ReadAll(rc)
			if err != nil {
				return nil, fmt.Errorf("read binary: %w", err)
			}
			return data, nil
		}
	}
	return nil, fmt.Errorf("binary %q not found in archive", name)

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Log or dump the first bytes of the downloaded body — if it starts with '<' or 'gzip magic' instead of 'PK', the URL or asset is wrong, not the zip parser
  2. Verify the download: check Content-Length vs bytes read, and validate the release checksum/signature before calling extractBinaryFromZip
  3. Re-download the release asset from the official URL and retry the update
  4. If building the release yourself, confirm the packaging step actually produced a .zip containing the binary at the expected entry name

Example fix

// before
buf, err := io.ReadAll(r)
// ...
zr, err := zip.NewReader(bytes.NewReader(buf), int64(len(buf)))
if err != nil {
    return nil, fmt.Errorf("zip reader: %w", err)
}

// after: fail fast on obviously-non-zip bodies before parsing
buf, err := io.ReadAll(r)
if err != nil {
    return nil, fmt.Errorf("read zip data: %w", err)
}
if len(buf) < 4 || !bytes.HasPrefix(buf, []byte("PK\x03\x04")) {
    return nil, fmt.Errorf("zip reader: downloaded body is not a zip archive (first bytes: %q)", buf[:min(len(buf), 16)])
}
Defensive patterns

Strategy: validation

Validate before calling

// Before calling extractBinaryFromZip, verify the download is a zip and complete.
func verifyZipBody(buf []byte, wantSize int64) error {
    if wantSize > 0 && int64(len(buf)) != wantSize {
        return fmt.Errorf("short download: got %d bytes, expected %d", len(buf), wantSize)
    }
    if len(buf) < 4 || !bytes.HasPrefix(buf, []byte("PK\x03\x04")) {
        return fmt.Errorf("body is not a zip archive (first bytes: %q)", buf[:min(len(buf), 16)])
    }
    return nil
}

Try / catch

In Go, check errors.Is(err, zip.ErrFormat) after the call to distinguish 'not a zip' from other failures and surface a 're-download the release' hint instead of a generic error.

Prevention

When it happens

Trigger: Calling the self-update flow (extractBinaryFromZip on a downloaded release stream) where the body is truncated mid-download, is a redirect/login page instead of the asset, or the GitHub asset URL pointed at a non-zip artifact (e.g. a bare .tar.gz or .exe served where a .zip was expected). Also a proxy or AV scanner rewriting the response.

Common situations: CDN/proxy returning a 200 HTML page for the asset URL, flaky network cutting the body early, release publishing mistake (wrong asset uploaded), or a manual test pointing the update URL at a local file that is not a zip.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/1fb86410fb59304d. Report an issue: GitHub.