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

  1. Only send req.URL in Patch before the transfer starts; send req with only Labels/Proxy afterwards
  2. If re-sending the same URL, omit the URL field (empty string passes the check)
  3. To change the link, delete the task and create a new one from the new ed2k URL
  4. 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

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


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/78604d35f0142865. Report an issue: GitHub.