GopeedLab/gopeed · error
cannot change ed2k link after transfer started
Error message
cannot change ed2k link after transfer started
What it means
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.
Source
Thrown at internal/protocol/ed2k/fetcher.go:141
return err
}
f.handle = handle
if handle.IsValid() && handle.IsPaused() {
if err := client.ResumeTransfer(handle.GetHash()); err != nil {
return err
}
}
return nil
}
func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
handle := f.currentHandle()
if opts != nil && (opts.Name != "" || opts.Path != "") && handle.IsValid() {
return errors.New("cannot change ed2k target path after transfer started")
}
if req != nil && req.URL != "" && handle.IsValid() {
return errors.New("cannot change ed2k link after transfer started")
}
if req != nil {
if req.URL != "" {
link, err := parseLink(req.URL)
if err != nil {
return err
}
f.meta.Req.URL = req.URL
f.meta.Res = buildResource(link)
}
if req.Labels != nil {
if f.meta.Req.Labels == nil {
f.meta.Req.Labels = make(map[string]string)
}
for k, v := range req.Labels {
f.meta.Req.Labels[k] = v
}View on GitHub (pinned to 7b7327ffb3)
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
Example fix
// before
err := downloader.Patch(taskId, &base.Request{URL: link, Labels: lbls}, nil) // URL present -> rejected
// after (started task: labels only)
err := downloader.Patch(taskId, &base.Request{Labels: lbls}, nil) Defensive patterns
Strategy: validation
Validate before calling
// Only include URL in the patch request before the transfer starts:
patchReq := &base.Request{Labels: lbls}
if !started { patchReq.URL = newURL } // omit once started, even if unchanged
err := downloader.Patch(taskId, patchReq, nil) Try / catch
if err := downloader.Patch(taskId, req, nil); err != nil {
if strings.Contains(err.Error(), "cannot change ed2k link") {
// recreate the task from the new link instead
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot change ed2k target path after transfer started
- unsupported ed2k link type
- NAVIGATION_FAILED
- invalid blob url
- blob source not found
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/78604d35f0142865.
Report an issue: GitHub.