AlistGo/alist · error
open download file failed: %w
Error message
open download file failed: %w
What it means
Inside the Link() range-reader, three retried attempts to open a Mega download (d.c.NewDownload) all failed. This occurs lazily at first byte-read, not at Link creation. NewDownload fails when the node's server-side download slot cannot be established: node removed, API/session problem, or Mega-side capacity issues.
Source
Thrown at drivers/mega/driver.go:107
//down, err := d.c.NewDownload(n.Node)
//if err != nil {
// return nil, fmt.Errorf("open download file failed: %w", err)
//}
size := file.GetSize()
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
length := httpRange.Length
if httpRange.Length >= 0 && httpRange.Start+httpRange.Length >= size {
length = -1
}
var down *mega.Download
err := utils.Retry(3, time.Second, func() (err error) {
down, err = d.c.NewDownload(node.n)
return err
})
if err != nil {
return nil, fmt.Errorf("open download file failed: %w", err)
}
oo := &openObject{
ctx: ctx,
d: down,
skip: httpRange.Start,
}
return readers.NewLimitedReadCloser(oo, length), nil
}
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader}
resultLink := &model.Link{
RangeReadCloser: resultRangeReadCloser,
}
return resultLink, nil
}
return nil, fmt.Errorf("unable to convert dir to mega n")
}
View on GitHub (pinned to 843d9dc814)
Solutions
- Retry the whole Link (or re-List the parent) — a fresh NewDownload with a current node often succeeds
- Check the node still exists via d.c.FS before retrying; if gone, surface not-found instead of retry
- Widen the retry window/backoff (e.g. 5 attempts with exponential backoff) for transient Mega errors
- Avoid caching RangeReadCloser-producing Links for long periods; fetch Link close to read time
Example fix
// before
err := utils.Retry(3, time.Second, func() (err error) {
down, err = d.c.NewDownload(node.n)
return err
})
if err != nil {
return nil, fmt.Errorf("open download file failed: %w", err)
}
// after
err := utils.Retry(5, 2*time.Second, func() (err error) {
down, err = d.c.NewDownload(node.n)
if err != nil && strings.Contains(err.Error(), "not found") {
return err // permanent: don't retry
}
return err
})
if err != nil {
return nil, fmt.Errorf("open download file failed (node %s): %w", node.n.GetID(), err)
} Defensive patterns
Strategy: retry
Validate before calling
// Validate node before opening a download
if node.n == nil || node.n.GetType() == mega.FILE && !node.IsFile() {
return nil, errs.ObjectNotFound
} Try / catch
// Fresh Link on retryable open failures
rc, err := openRange(ctx, link)
if err != nil && strings.Contains(err.Error(), "open download file failed") {
if fresh, gerr := d.Get(ctx, path); gerr == nil {
if newLink, lerr := d.Link(ctx, fresh, args); lerr == nil {
rc, err = openRange(ctx, newLink)
}
}
} Prevention
- Fetch Link close to read time; don't cache long-lived range readers
- Verify node existence before retry storms
- Use exponential backoff wider than 3x1s for transient Mega errors
When it happens
Trigger: File deleted or moved between Link creation and the first ranged read; Mega session expired by read time; NewDownload consistently failing with a node in a non-downloadable state (e.g. suspended account or taken-down file); transient server errors outlasting the 3x1s retry window.
Common situations: Media players issuing Link then seeking much later; long-lived Link objects cached by the frontend; network flaps longer than the 3-second retry budget; files removed upstream while queued.
Related errors
- chunk_%d: giving up after %d retries while server is overloa
- http request failure,status: %d
- chunk download size incorrect, expected=%d, got=%d
- missing cookie or qrcode account
- server returns no url
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/47b43c607b0a7a5e.
Report an issue: GitHub.