AlistGo/alist · error

empty file id

Error message

empty file id

What it means

Streamtape's Link() extracts the numeric file ID from the object's composite ID using fileIDFromObjID, which strips an optional 'f:' prefix and returns the rest. It returns "" only when the stored object ID itself is empty, meaning the model.Obj passed in was constructed without an ID — the driver then cannot call /file/dlticket to mint a download ticket.

Source

Thrown at drivers/streamtape/driver.go:94

		objects = append(objects, &model.Object{
			ID:       encodeFolderID(f.ID),
			Name:     f.Name,
			IsFolder: true,
		})
	}
	for _, f := range result.Files {
		objects = append(objects, buildFileObj(f))
	}
	return objects, nil
}

func (d *Streamtape) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	if file.IsDir() {
		return nil, errs.NotFile
	}
	fileID := fileIDFromObjID(file.GetID())
	if fileID == "" {
		return nil, errors.New("empty file id")
	}

	var ticket dlTicketResult
	if err := d.callAPI(ctx, "/file/dlticket", map[string]string{"file": fileID}, &ticket); err != nil {
		return nil, err
	}

	var dl dlResult
	waitSeconds := ticket.WaitTime
	if waitSeconds > 0 {
		timer := time.NewTimer(time.Duration(waitSeconds+1) * time.Second)
		select {
		case <-ctx.Done():
			timer.Stop()
			return nil, ctx.Err()
		case <-timer.C:
		}
	}

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Always pass the exact model.Obj returned by the driver's List() to Link()
  2. If building objects manually, set ID via encodeFileID("<numeric streamtape file id>")
  3. Clear the listing cache for that path if a cached object lost its ID

Example fix

// before
file := &model.Object{Name: "video.mp4", Size: 123}
link, err := d.Link(ctx, file, args) // -> "empty file id"

// after
file := &model.Object{ID: encodeFileID("abc123XYZ"), Name: "video.mp4", Size: 123}
link, err := d.Link(ctx, file, args)
Defensive patterns

Strategy: validation

Validate before calling

id := fileIDFromObjID(file.GetID())
if id == "" {
    return fmt.Errorf("object %q has no streamtape file id; refresh listing and pass the object returned by List", file.GetName())
}

Type guard

func hasStreamtapeFileID(o model.Obj) bool {
    id := o.GetID()
    return id != "" && fileIDFromObjID(id) != ""
}

Try / catch

link, err := d.Link(ctx, file, args)
if err != nil && err.Error() == "empty file id" {
    // re-list to get a well-formed object, then retry once
    objs, lerr := d.List(ctx, parentDir, model.ListArgs{})
    if lerr == nil {
        for _, o := range objs {
            if o.GetName() == file.GetName() && o.GetID() != "" {
                link, err = d.Link(ctx, o, args)
            }
        }
    }
}

Prevention

When it happens

Trigger: Calling Link() with a hand-built model.Object (no ID set), or an object produced by a code path that returns model.URL wrappers or placeholder objects that never got an 'f:<id>' ID. Objects returned by List()/getFiles always carry encodeFileID and cannot trigger it.

Common situations: Custom scripts or extensions that synthesize an Obj instead of taking one from List(); Cache serving a stale/partial entry where the ID field was dropped; Renamed or copied objects routed through paths that lose the ID before Link

Related errors


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