{"record":{"id":"d7f32f128e92d6fc","repo":"AlistGo/alist","slug":"empty-file-id","errorCode":null,"errorMessage":"empty file id","messagePattern":"empty file id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/streamtape/driver.go","lineNumber":94,"sourceCode":"\t\tobjects = append(objects, &model.Object{\n\t\t\tID:       encodeFolderID(f.ID),\n\t\t\tName:     f.Name,\n\t\t\tIsFolder: true,\n\t\t})\n\t}\n\tfor _, f := range result.Files {\n\t\tobjects = append(objects, buildFileObj(f))\n\t}\n\treturn objects, nil\n}\n\nfunc (d *Streamtape) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {\n\tif file.IsDir() {\n\t\treturn nil, errs.NotFile\n\t}\n\tfileID := fileIDFromObjID(file.GetID())\n\tif fileID == \"\" {\n\t\treturn nil, errors.New(\"empty file id\")\n\t}\n\n\tvar ticket dlTicketResult\n\tif err := d.callAPI(ctx, \"/file/dlticket\", map[string]string{\"file\": fileID}, &ticket); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar dl dlResult\n\twaitSeconds := ticket.WaitTime\n\tif waitSeconds > 0 {\n\t\ttimer := time.NewTimer(time.Duration(waitSeconds+1) * time.Second)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\ttimer.Stop()\n\t\t\treturn nil, ctx.Err()\n\t\tcase <-timer.C:\n\t\t}\n\t}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/streamtape/driver.go#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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","solutions":["Always pass the exact model.Obj returned by the driver's List() to Link()","If building objects manually, set ID via encodeFileID(\"<numeric streamtape file id>\")","Clear the listing cache for that path if a cached object lost its ID"],"exampleFix":"// before\nfile := &model.Object{Name: \"video.mp4\", Size: 123}\nlink, err := d.Link(ctx, file, args) // -> \"empty file id\"\n\n// after\nfile := &model.Object{ID: encodeFileID(\"abc123XYZ\"), Name: \"video.mp4\", Size: 123}\nlink, err := d.Link(ctx, file, args)","handlingStrategy":"validation","validationCode":"id := fileIDFromObjID(file.GetID())\nif id == \"\" {\n    return fmt.Errorf(\"object %q has no streamtape file id; refresh listing and pass the object returned by List\", file.GetName())\n}","typeGuard":"func hasStreamtapeFileID(o model.Obj) bool {\n    id := o.GetID()\n    return id != \"\" && fileIDFromObjID(id) != \"\"\n}","tryCatchPattern":"link, err := d.Link(ctx, file, args)\nif err != nil && err.Error() == \"empty file id\" {\n    // re-list to get a well-formed object, then retry once\n    objs, lerr := d.List(ctx, parentDir, model.ListArgs{})\n    if lerr == nil {\n        for _, o := range objs {\n            if o.GetName() == file.GetName() && o.GetID() != \"\" {\n                link, err = d.Link(ctx, o, args)\n            }\n        }\n    }\n}","preventionTips":["Always call Link with objects obtained from List()","Never construct model.Object for driver calls without setting the ID","Invalidate cached objects that lose their ID after re-serialization"],"tags":["streamtape","object-id","link","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}