AlistGo/alist · error

empty download url

Error message

empty download url

What it means

During Link() the driver had a cached *Object whose DownloadURL was empty, so it re-fetched the file content via the Gitee contents API; the fresh response also had an empty DownloadURL, meaning Gitee did not supply a raw-file link for this path. The driver refuses to return a link that cannot be downloaded.

Source

Thrown at drivers/gitee/driver.go:90

	objs := make([]model.Obj, 0, len(contents))
	for i := range contents {
		objs = append(objs, contents[i].toModelObj())
	}
	return objs, nil
}

func (d *Gitee) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	var downloadURL string
	if obj, ok := file.(*Object); ok {
		downloadURL = obj.DownloadURL
		if downloadURL == "" {
			relPath := d.relativePath(file.GetPath())
			content, err := d.getContent(relPath)
			if err != nil {
				return nil, err
			}
			if content.DownloadURL == "" {
				return nil, errors.New("empty download url")
			}
			obj.DownloadURL = content.DownloadURL
			downloadURL = content.DownloadURL
		}
	} else {
		relPath := d.relativePath(file.GetPath())
		content, err := d.getContent(relPath)
		if err != nil {
			return nil, err
		}
		if content.DownloadURL == "" {
			return nil, errors.New("empty download url")
		}
		downloadURL = content.DownloadURL
	}
	url := d.applyProxy(downloadURL)
	return &model.Link{
		URL: url,

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Verify the access token is configured and has read access to the private repo (contents API then returns download_url).
  2. Check whether the file is empty or an LFS pointer; if so, download via git or configure LFS handling rather than the raw URL.
  3. Re-list the directory so the cached Object is refreshed from a current API response.
  4. If the API response shape changed, update the Content struct mapping in the driver.
Defensive patterns

Strategy: try-catch

Validate before calling

if obj, ok := file.(*Object); ok && obj.DownloadURL != "" {
    // fast path: no API call needed
    return &model.Link{URL: d.applyProxy(obj.DownloadURL)}, nil
}

Type guard

func hasDownloadURL(o model.Obj) bool {
    if obj, ok := o.(*Object); ok {
        return obj.DownloadURL != ""
    }
    return false
}

Try / catch

link, err := d.Link(ctx, file, args)
if err != nil && err.Error() == "empty download url" {
    // refresh listing and retry once
    op.RefreshDir(ctx, parentPath)
    link, err = d.Link(ctx, file, args)
}

Prevention

When it happens

Trigger: Calling Link() on a file whose *Object.DownloadURL is empty AND whose GET /repos/{owner}/{repo}/contents/{path} response has an empty download_url — typical for empty files, LFS-pointer files, files in private repos with no token, or unsupported content encodings. drivers/gitee/driver.go:90.

Common situations: Zero-byte file where Gitee omits download_url; private repository accessed without an access token so the contents API returns limited fields; file stored with Git LFS where the API returns a pointer instead of a blob URL; API change in the contents response shape.

Related errors


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