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

  1. Retry the whole Link (or re-List the parent) — a fresh NewDownload with a current node often succeeds
  2. Check the node still exists via d.c.FS before retrying; if gone, surface not-found instead of retry
  3. Widen the retry window/backoff (e.g. 5 attempts with exponential backoff) for transient Mega errors
  4. 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

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


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/47b43c607b0a7a5e. Report an issue: GitHub.