{"record":{"id":"78604d35f0142865","repo":"GopeedLab/gopeed","slug":"cannot-change-ed2k-link-after-transfer-started","errorCode":null,"errorMessage":"cannot change ed2k link after transfer started","messagePattern":"cannot change ed2k link after transfer started","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/protocol/ed2k/fetcher.go","lineNumber":141,"sourceCode":"\t\treturn err\n\t}\n\tf.handle = handle\n\tif handle.IsValid() && handle.IsPaused() {\n\t\tif err := client.ResumeTransfer(handle.GetHash()); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {\n\thandle := f.currentHandle()\n\n\tif opts != nil && (opts.Name != \"\" || opts.Path != \"\") && handle.IsValid() {\n\t\treturn errors.New(\"cannot change ed2k target path after transfer started\")\n\t}\n\tif req != nil && req.URL != \"\" && handle.IsValid() {\n\t\treturn errors.New(\"cannot change ed2k link after transfer started\")\n\t}\n\n\tif req != nil {\n\t\tif req.URL != \"\" {\n\t\t\tlink, err := parseLink(req.URL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tf.meta.Req.URL = req.URL\n\t\t\tf.meta.Res = buildResource(link)\n\t\t}\n\t\tif req.Labels != nil {\n\t\t\tif f.meta.Req.Labels == nil {\n\t\t\t\tf.meta.Req.Labels = make(map[string]string)\n\t\t\t}\n\t\t\tfor k, v := range req.Labels {\n\t\t\t\tf.meta.Req.Labels[k] = v\n\t\t\t}","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/protocol/ed2k/fetcher.go#L123-L159","documentation":"The ed2k Fetcher.Patch (internal/protocol/ed2k/fetcher.go:140-142) rejects a new req.URL once the transfer has a valid handle. The ed2k URL is the identity of the download (it encodes the file hash and size), so swapping it on a started transfer would desynchronize the fetcher's meta from the running transfer; Patch refuses instead.","triggerScenarios":"Downloader.Patch(taskId, &base.Request{URL: \"ed2k://|file|...\"}, nil) on an ed2k task whose transfer already exists (running or paused); generic 'edit task URL' UI applied to ed2k tasks; automation that re-submits the original request URL on patch — note even re-sending the identical URL is rejected because the check is URL != \"\" && handle.IsValid(), not URL-changed.","commonSituations":"Retargeting a dead ed2k link to a mirror link of the same file; a client that always sends the full request object on every Patch call; copying a magnet-style 'replace link' workflow from BT/HTTP tasks to ed2k.","solutions":["Only send req.URL in Patch before the transfer starts; send req with only Labels/Proxy afterwards","If re-sending the same URL, omit the URL field (empty string passes the check)","To change the link, delete the task and create a new one from the new ed2k URL","In generic UI code, blank out req.URL when patching started ed2k tasks"],"exampleFix":"// before\nerr := downloader.Patch(taskId, &base.Request{URL: link, Labels: lbls}, nil) // URL present -> rejected\n\n// after (started task: labels only)\nerr := downloader.Patch(taskId, &base.Request{Labels: lbls}, nil)","handlingStrategy":"validation","validationCode":"// Only include URL in the patch request before the transfer starts:\npatchReq := &base.Request{Labels: lbls}\nif !started { patchReq.URL = newURL } // omit once started, even if unchanged\nerr := downloader.Patch(taskId, patchReq, nil)","typeGuard":null,"tryCatchPattern":"if err := downloader.Patch(taskId, req, nil); err != nil {\n    if strings.Contains(err.Error(), \"cannot change ed2k link\") {\n        // recreate the task from the new link instead\n    }\n}","preventionTips":["Never resend the original URL in Patch for started ed2k tasks (even identical URLs are rejected)","To switch links, delete and re-create the task","Keep 'edit URL' affordances off for started ed2k tasks in UIs"],"tags":["ed2k","patch","url","lifecycle"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}