{"record":{"id":"13fcc3e1da2c2b29","repo":"GopeedLab/gopeed","slug":"invalid-resource-id","errorCode":null,"errorMessage":"invalid resource id","messagePattern":"invalid resource id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/downloader.go","lineNumber":531,"sourceCode":"\t\topts := ir.Opts\n\t\tif opts == nil {\n\t\t\topts = req.Opts\n\t\t}\n\t\ttaskId, err := d.CreateDirect(ir.Req, opts.Clone())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttaskIds = append(taskIds, taskId)\n\t}\n\treturn taskIds, nil\n}\n\nfunc (d *Downloader) Create(rrId string) (taskId string, err error) {\n\td.fetcherMapLock.RLock()\n\tfetcher, ok := d.fetcherCache[rrId]\n\td.fetcherMapLock.RUnlock()\n\tif !ok {\n\t\treturn \"\", errors.New(\"invalid resource id\")\n\t}\n\tdefer func() {\n\t\td.fetcherMapLock.Lock()\n\t\tdelete(d.fetcherCache, rrId)\n\t\td.fetcherMapLock.Unlock()\n\t}()\n\treturn d.doCreate(fetcher, nil)\n}\n\n// Patch modifies task-specific data based on the protocol.\n// For HTTP protocol, it can modify Request info.\n// For BT protocol, it can modify SelectFiles.\nfunc (d *Downloader) Patch(id string, req *base.Request, opts *base.Options) error {\n\ttask := d.GetTask(id)\n\tif task == nil {\n\t\treturn ErrTaskNotFound\n\t}\n\tif err := func() error {","sourceCodeStart":513,"sourceCodeEnd":549,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/downloader.go#L513-L549","documentation":"Downloader.Create(rrId) (pkg/download/downloader.go:526-539) looks rrId up in fetcherCache, which is populated only by a successful Resolve (downloader.go:458-460) and consumed destructively: Create deletes the entry (deferred delete at lines 533-537), so each resolve-result id is single-use. An unknown or already-used rrId yields 'invalid resource id'.","triggerScenarios":"Calling Create twice with the same rrId (first call succeeds and evicts the cache entry); calling Create without a prior successful Resolve; using an rrId from a different Downloader instance or from before a restart (the cache is in-memory only); racing two Creates on the same rrId — one wins, the other gets this error.","commonSituations":"Client retries the create call after a network blip and the first attempt actually succeeded; UI 'confirm download' button double-clicked; splitting resolve and create across processes/sessions; confusing the ResolveResult.ID with the taskId or the blob URL id.","solutions":["Call Create exactly once per Resolve, immediately after Resolve returns; treat its result as the taskId","Make the operation idempotent on the client: if Create fails with this error, re-Resolve the request to get a fresh rrId","Disable/serialize the confirm button while a Create is in flight","Never persist or reuse rrIds across sessions — they are ephemeral handles"],"exampleFix":"// before\nrr, _ := d.Resolve(req, nil)\n_, err1 := d.Create(rr.ID)\n_, err2 := d.Create(rr.ID) // \"invalid resource id\"\n\n// after\nrr, err := d.Resolve(req, nil)\nif err != nil { return \"\", err }\ntaskId, err := d.Create(rr.ID)\nif errors.Is(err, /* invalid resource id */ errInvalidRR) || err != nil {\n    rr, err = d.Resolve(req, nil) // re-resolve, then Create once\n    if err != nil { return \"\", err }\n    return d.Create(rr.ID)\n}","handlingStrategy":"validation","validationCode":"rr, err := downloader.Resolve(req, opts)\nif err != nil { return \"\", err }\n// rr.ID is single-use: create immediately, exactly once\ntaskId, err := downloader.Create(rr.ID)\nif err != nil {\n    // do NOT reuse rr.ID; re-resolve if needed\n}","typeGuard":null,"tryCatchPattern":"taskId, err := downloader.Create(rrId)\nif err != nil && strings.Contains(err.Error(), \"invalid resource id\") {\n    // rrId unknown or consumed: re-Resolve to get a fresh id, then Create once\n    rr, rerr := downloader.Resolve(req, opts)\n    if rerr != nil { return \"\", rerr }\n    return downloader.Create(rr.ID)\n}","preventionTips":["Create exactly once, immediately after Resolve (the cache entry is deleted on use)","Guard double-clicks/retries in UI so Create is not reissued with the same rrId","Never persist rrIds; they live only in the Downloader's in-memory cache"],"tags":["api","resolve","single-use","not-found"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}