multica-ai/multica · error
verify download: %w
Error message
verify download: %w
What it means
After buffering the archive, verifyAssetSHA256 recomputes the SHA-256 and compares it against the manifest entry; mismatch is wrapped as 'verify download: %w'. This guard intentionally runs before extraction so untrusted bytes never reach gzip/tar/zip parsing. On failure nothing is written and the binary is left untouched; the poller retries later.
Source
Thrown at server/internal/cli/update.go:427
}
// Buffer the archive into memory so we can verify the full SHA-256
// before writing anything to disk. Release archives are ~10–30 MB; the
// extraction code already buffers zip archives in full (random access
// requirement), so this is not a new memory cost on Windows. For tar.gz
// it adds a single in-RAM copy, which is preferable to running the
// untrusted bytes through gzip+tar extraction before the SHA-256 check.
archiveData, err := fetchURLBytes(downloadURL, timeout)
if err != nil {
return "", fmt.Errorf("download failed: %w", err)
}
if err := verifyAssetSHA256(archiveData, expectedSum, assetName); err != nil {
// Do NOT extract or replace; the next poll tick will retry. A
// corrupted asset is rare enough that retrying through the same
// CDN is the right default; persistent failures will surface in
// the daemon log.
return "", fmt.Errorf("verify download: %w", err)
}
// Extract the binary from the archive.
binaryName := "multica"
if runtime.GOOS == "windows" {
binaryName = "multica.exe"
}
var binaryData []byte
if runtime.GOOS == "windows" {
binaryData, err = extractBinaryFromZip(bytes.NewReader(archiveData), binaryName)
} else {
binaryData, err = extractBinaryFromTarGz(bytes.NewReader(archiveData), binaryName)
}
if err != nil {
return "", fmt.Errorf("extract binary: %w", err)
}
// Atomic replace: write to temp file, then rename over the original.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Retry on the next poll tick — the code deliberately does not extract or replace on mismatch, and transient corruption usually clears.
- Compare the downloaded asset's SHA-256 manually (curl the asset, shasum -a 256) against checksums.txt to confirm a stale manifest.
- If the manifest is stale, re-run the release job to regenerate checksums.txt for all assets.
- Disable any content-rewriting proxy for objects.githubusercontent.com.
Example fix
null
Defensive patterns
Strategy: retry
Type guard
func isVerifyFailure(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "verify download")
} Try / catch
out, err := cli.UpdateViaDownload(ver)
if err != nil && isVerifyFailure(err) {
// binary untouched by design; retry via same CDN on next poll tick.
// persistent failure => stale checksums.txt on the release: re-release.
return scheduleRetry(err)
} Prevention
- Always regenerate checksums.txt when re-uploading any release asset
- Never bypass the SHA-256 check to 'get the update through'
- Alert when verification fails persistently — it indicates release-side drift
When it happens
Trigger: A truncated or corrupted download (connection cut mid-body with a misleading Content-Length); an asset re-uploaded after checksums.txt was generated so the manifest is stale; a proxy/MITM mangling the body; a manifest collision on duplicate filename entries.
Common situations: Flaky networks delivering partial bodies; release pipelines that upload archives, then regenerate one asset without refreshing checksums.txt; corporate TLS-inspection appliances altering content.
Related errors
- checksum mismatch for %q: expected %s, got %s
- download checksum manifest: %w
- get attachment: %w
- attachment has no download URL
- download file: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/1ac0c1ec68e69a43.
Report an issue: GitHub.