Billionmail/BillionMail · error
download file failed:
Error message
download file failed:
What it means
The DownloadFile helper in core/internal/service/public/common.go performs an HTTP GET and checks the response status. When the status code is not in the 2xx range (below 200 or >= 300), it returns "download file failed: <resp.Status>" instead of writing the body to dst. The remote response body is discarded, so the original server error text is only available via resp.Status.
Source
Thrown at core/internal/service/public/common.go:2050
// Set request headers
if headers != nil {
for k, v := range headers {
req.Header.Set(k, v)
}
}
// Send request
var resp *http.Response
if resp, err = client.Do(req.WithContext(ctx)); err != nil {
return
}
defer resp.Body.Close()
// Download failed
if resp.StatusCode < http.StatusOK && resp.StatusCode >= http.StatusMultipleChoices {
return errors.New("download file failed: " + resp.Status)
}
// Create directory
dir := filepath.Dir(dst)
if !FileExists(dir) {
if err = os.MkdirAll(dir, 0755); err != nil {
return
}
}
// Create temporary file
tmpFile := dst + ".tmp"
// Open file
var fp *os.File
if fp, err = os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
return
}View on GitHub (pinned to fc36c76c05)
Solutions
- Check the URL is correct and the asset still exists (curl -I the URL to see the status).
- For 403/429, add required auth headers or wait for the rate limit; consider passing an authenticated client to the download call.
- Verify network/proxy settings; use the returned resp.Status string to identify the exact HTTP code before retrying.
- For 301/302, ensure the final redirect target is reachable and does not loop.
Example fix
// before
err := public.DownloadFile("https://example.com/old-file.tar.gz", dst) // 404
// after
url := "https://example.com/new-file-v2.tar.gz"
if err := public.DownloadFile(url, dst); err != nil {
log.Printf("download %s failed: %v", url, err)
return fmt.Errorf("check asset exists at %s: %w", url, err)
} Defensive patterns
Strategy: retry
Validate before calling
resp, err := http.Head(url)
if err != nil || resp.StatusCode != http.StatusOK {
return fmt.Errorf("asset not downloadable, status: %d", resp.StatusCode)
} Try / catch
var lastErr error
for attempt := 0; attempt < 3; attempt++ {
err := public.DownloadFile(url, dst)
if err == nil {
return nil
}
lastErr = err
if strings.Contains(err.Error(), "4") && !strings.Contains(err.Error(), "429") {
break // client errors won't fix themselves
}
time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
}
return lastErr Prevention
- HEAD-check the URL before downloading
- Pin asset URLs to immutable versions rather than moving 'latest' links
- Log resp.Status details included in the error for diagnosis
- Handle 429 with exponential backoff
When it happens
Trigger: Calling DownloadFile(url, dst) where the server replies 404 Not Found, 403 Forbidden, 500, 301/302 terminated as non-2xx, or any non-2xx status; also firewalled/proxied endpoints returning HTML error pages with 4xx/5xx codes.
Common situations: Downloading an installer/asset from a URL that was renamed or deleted, GitHub release asset URLs that moved, rate-limited endpoints returning 429, or self-signed/intercepting proxies returning 403.
Related errors
- base URL accessibility test failed: %w
- download lipsync video: %w
- base URL not configured
- request context is nil
- API key validation failed: %w
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/d45586d1d4169639.
Report an issue: GitHub.