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

  1. Retry on the next poll tick — the code deliberately does not extract or replace on mismatch, and transient corruption usually clears.
  2. Compare the downloaded asset's SHA-256 manually (curl the asset, shasum -a 256) against checksums.txt to confirm a stale manifest.
  3. If the manifest is stale, re-run the release job to regenerate checksums.txt for all assets.
  4. 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

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


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