GopeedLab/gopeed · error

cannot change ed2k target path after transfer started

Error message

cannot change ed2k target path after transfer started

What it means

The ed2k Fetcher.Patch (internal/protocol/ed2k/fetcher.go:137-139) rejects changes to opts.Name or opts.Path once the transfer has a valid handle in the underlying goed2k client (currentHandle() finds a live or paused transfer). An eMule transfer is bound to the file path it was added with, so renaming or re-targeting it after start cannot be supported and is refused rather than silently ignored.

Source

Thrown at internal/protocol/ed2k/fetcher.go:138

	}
	handle, err = client.AddTransfer(atp)
	if err != nil {
		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)
			}

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Patch name/path only before the task has started (no valid handle yet) — right after Resolve, before Start
  2. For a running task: pause, remove/close the task, and re-create it with the new Name/Path from the same ed2k link
  3. Restrict UI patch fields for ed2k to Labels and Proxy, which Patch does accept at any time
  4. Check task status first and hide/disable rename/repath controls for started ed2k tasks

Example fix

// before
err := downloader.Patch(taskId, nil, &base.Options{Path: "/data/new-dir"}) // fails once started

// after (restart the task with new path)
_ = downloader.Pause(&download.TaskFilter{IDs: []string{taskId}})
_ = downloader.Delete(&download.TaskFilter{IDs: []string{taskId}})
_, err = downloader.CreateDirect(req, &base.Options{Path: "/data/new-dir"})
Defensive patterns

Strategy: validation

Validate before calling

// Before patching an ed2k task with name/path, ensure it has not started:
task := downloader.GetTask(taskId)
if task == nil { return download.ErrTaskNotFound }
started := task.Status != base.DownloadStatusReady /* adjust to your states */
if started && (opts.Name != "" || opts.Path != "") {
    return errors.New("cannot change path of a started ed2k task")
}

Try / catch

if err := downloader.Patch(taskId, nil, opts); err != nil {
    if strings.Contains(err.Error(), "cannot change ed2k target path") {
        // pause + delete + re-create with new options
    }
}

Prevention

When it happens

Trigger: Downloader.Patch(taskId, nil, &base.Options{Name: "x"}) or with Path set, on an ed2k task whose transfer already exists (previously started, even if now paused); patching path/name on a re-created/restored ed2k task where FindTransfer immediately returns a valid handle; UI flows that let users move a running download to another directory.

Common situations: A 'change save folder' or 'rename' button applied to a running or paused ed2k task; automation that patches Options (e.g. setting Path from a template) on every task regardless of protocol/state; restoring tasks from storage and then applying pending option changes.

Related errors


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