multica-ai/multica · error

download failed: %w

Error message

download failed: %w

What it means

The release archive itself is fetched with fetchURLBytes; failure is wrapped as 'download failed: %w'. Underlying causes are transport errors (timeout at DefaultUpdateDownloadTimeout of 120s, connection reset, DNS) or the 'HTTP %d from %s' non-200 error. The archive is buffered in memory (~10–30 MB) before any disk write.

Source

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

	timeout := updateDownloadTimeoutOrDefault(downloadTimeout)
	manifestData, err := fetchURLBytes(manifestAsset.BrowserDownloadURL, timeout)
	if err != nil {
		return "", fmt.Errorf("download checksum manifest: %w", err)
	}
	expectedSum, err := parseChecksumManifest(manifestData, assetName)
	if err != nil {
		return "", fmt.Errorf("parse checksum manifest: %w", err)
	}

	// 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)

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. If a custom timeout was passed, raise it or pass 0 to get the 120s default via updateDownloadTimeoutOrDefault.
  2. Verify the archive URL from the error's wrapped message downloads in a browser/curl.
  3. Retry — transient CDN resets and half-published releases resolve on the next poll tick.
  4. Check egress rules for objects.githubusercontent.com in firewalled environments.

Example fix

// before
out, err := cli.UpdateViaDownloadWithTimeout(ver, 5*time.Second) // too short for 30MB

// after
out, err := cli.UpdateViaDownloadWithTimeout(ver, 0) // uses DefaultUpdateDownloadTimeout (120s)
Defensive patterns

Strategy: retry

Try / catch

out, err := cli.UpdateViaDownloadWithTimeout(ver, timeout)
if err != nil {
    if strings.HasPrefix(err.Error(), "download failed") {
        var ne net.Error
        if errors.As(err, &ne) && ne.Timeout() {
            // raise timeout on retry (0 selects the 120s default)
            out, err = cli.UpdateViaDownloadWithTimeout(ver, 0)
        }
    }
}

Prevention

When it happens

Trigger: Slow links exceeding the 120s timeout on a ~30 MB archive; the asset 404ing after the metadata was fetched (release edited mid-update); interrupted connections; a caller passing a short custom downloadTimeout to UpdateViaDownloadWithTimeout.

Common situations: Metered or congested networks; CI environments with restricted egress that allow the API but block the objects CDN (objects.githubusercontent.com); updates attempted while the release assets were being replaced.

Related errors


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