multica-ai/multica · error

HTTP %d from %s

Error message

HTTP %d from %s

What it means

fetchURLBytes performs an HTTP GET with a timeout and returns 'HTTP %d from %s' whenever the response status is not 200. It is the shared fetcher for the checksum manifest and the release archive, so any non-OK status from the GitHub release CDN surfaces here. The URL is included, so you can tell exactly which artifact failed.

Source

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

	if timeout <= 0 {
		return DefaultUpdateDownloadTimeout
	}
	return timeout
}

// fetchURLBytes does a GET with the given timeout and returns the response
// body in full. Used for the checksum manifest (tiny) and the release
// archive (single-digit MB). The checksum verification path requires buffered
// bytes so streaming would just push the buffer into the caller anyway.
func fetchURLBytes(url string, timeout time.Duration) ([]byte, error) {
	client := &http.Client{Timeout: updateDownloadTimeoutOrDefault(timeout)}
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP %d from %s", resp.StatusCode, url)
	}
	return io.ReadAll(resp.Body)
}

// UpdateViaDownload downloads the latest release binary from GitHub and replaces
// the current executable in-place. Returns the combined output message and any error.
func UpdateViaDownload(targetVersion string) (string, error) {
	return UpdateViaDownloadWithTimeout(targetVersion, DefaultUpdateDownloadTimeout)
}

// UpdateViaDownloadWithTimeout downloads the latest release binary with a caller-selected timeout.
func UpdateViaDownloadWithTimeout(targetVersion string, downloadTimeout time.Duration) (string, error) {
	// Determine current binary path.
	exePath, err := selfexec.Resolve()
	if err != nil {
		return "", fmt.Errorf("resolve executable path: %w", err)
	}
	exePath, err = filepath.EvalSymlinks(exePath)

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check the embedded URL — is it the asset that actually exists on the release page (tag matches, asset name matches GOOS/GOARCH)?
  2. For 403 rate limiting, wait for the rate-limit window to reset or authenticate GitHub requests via a token.
  3. For 404 on a freshly cut release, wait for the release pipeline to finish uploading all assets, then retry — the poller's next tick retries automatically.
  4. For persistent 5xx, retry with backoff or switch networks; CDN failures are usually transient.

Example fix

null
Defensive patterns

Strategy: retry

Type guard

func isHTTPStatusErr(err error) (int, bool) {
    // matches the fmt.Errorf("HTTP %d from %s", ...) convention
    var se *neturl.Error
    if errors.As(err, &se) {
        return 0, false
    }
    m := regexp.MustCompile(`^HTTP (\d+) from `).FindStringSubmatch(err.Error())
    if m == nil {
        return 0, false
    }
    n, _ := strconv.Atoi(m[1])
    return n, true
}

Try / catch

data, err := fetchURLBytes(url, timeout)
if err != nil {
    if code, ok := isHTTPStatusErr(err); ok && (code == 403 || code >= 500) {
        // rate-limited or server-side: safe to retry after a delay
        time.Sleep(30 * time.Second)
        data, err = fetchURLBytes(url, timeout)
    }
    if err != nil {
        return fmt.Errorf("fetch %s: %w", url, err)
    }
}

Prevention

When it happens

Trigger: 404 when the asset URL is stale or the release is a draft (draft assets need auth); 403 when GitHub API rate limits are exceeded; 5xx from the CDN during publish propagation; redirects to a deleted asset after a release was re-published.

Common situations: A half-published GoReleaser run where checksums.txt or the archive lags behind the tag; running the updater on many machines behind one NAT so the shared rate limit trips; pinning a targetVersion whose release assets were later replaced.

Related errors


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