AlistGo/alist · error

failed get torrent ID

Error message

failed get torrent ID

What it means

After TorrentAdd() succeeds at the RPC level, the tool requires torrent.ID to be non-nil to build its GID. transmission-daemon's torrent-add response (and especially the torrent-duplicate response when the torrent already exists) does not always include the id field, leaving ID nil even though the torrent exists. The tool then fails with this bare error.

Source

Thrown at internal/offline_download/transmission/client.go:115

		}
		// Flush last bytes
		if err = encoder.Close(); err != nil {
			return "", errors.Wrap(err, "can't flush last bytes of the base64 encoder")
		}
		// Get the string form
		b64 := buffer.String()
		rpcPayload.MetaInfo = &b64
	} else { // magnet uri
		rpcPayload.Filename = &args.Url
	}

	torrent, err := t.client.TorrentAdd(context.TODO(), rpcPayload)
	if err != nil {
		return "", err
	}

	if torrent.ID == nil {
		return "", fmt.Errorf("failed get torrent ID")
	}
	gid := strconv.FormatInt(*torrent.ID, 10)
	return gid, nil
}

func (t *Transmission) Remove(task *tool.DownloadTask) error {
	gid, err := strconv.ParseInt(task.GID, 10, 64)
	if err != nil {
		return err
	}
	err = t.client.TorrentRemove(context.TODO(), transmissionrpc.TorrentRemovePayload{
		IDs:             []int64{gid},
		DeleteLocalData: false,
	})
	return err
}

func (t *Transmission) Status(task *tool.DownloadTask) (*tool.Status, error) {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Check whether the torrent already exists (by hash) before adding, and reuse its id
  2. If already added, query TorrentGetAllFor / get by hash to obtain the real id
  3. Upgrade the transmissionrpc library — newer versions normalize the duplicate response
  4. As a workaround, remove the duplicate torrent first, then re-add

Example fix

// before
if torrent.ID == nil {
    return "", fmt.Errorf("failed get torrent ID")
}

// after: fall back to hash lookup for the duplicate case
if torrent.ID == nil && torrent.HashString != nil {
    infos, err := t.client.TorrentGetAllFor(ctx, nil)
    if err == nil {
        for _, i := range infos {
            if i.HashString != nil && *i.HashString == *torrent.HashString {
                return strconv.FormatInt(*i.ID, 10), nil
            }
        }
    }
}
if torrent.ID == nil {
    return "", fmt.Errorf("failed get torrent ID")
}
Defensive patterns

Strategy: validation

Validate before calling

torrent, err := t.client.TorrentAdd(context.TODO(), rpcPayload)
if err != nil {
    return "", err
}
if torrent.ID == nil {
    // likely a duplicate: resolve the existing torrent by hash instead of failing
    if torrent.HashString != nil {
        if infos, ierr := t.client.TorrentGetAllFor(context.TODO(), nil); ierr == nil {
            for _, i := range infos {
                if i.HashString != nil && *i.HashString == *torrent.HashString && i.ID != nil {
                    return strconv.FormatInt(*i.ID, 10), nil
                }
            }
        }
    }
    return "", fmt.Errorf("failed get torrent ID")
}

Type guard

func hasTorrentID(t *transmissionrpc.Torrent) bool {
    return t != nil && t.ID != nil
}

Try / catch

gid, err := t.AddURL(args)
if err != nil && strings.Contains(err.Error(), "failed get torrent ID") {
    // torrent probably already exists: ask the user or dedupe by hash
    return errorWithHint(err, "torrent may already exist in transmission; remove it or reuse its id")
}

Prevention

When it happens

Trigger: Adding a torrent/magnet that already exists in transmission (daemon returns 'torrent-duplicate' with only hash/name); a daemon or RPC edge case where the add response omits the id field.

Common situations: Re-adding the same magnet after a partial failure; retry logic that re-submits an existing torrent; seed-existing setups where the torrent is already loaded.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/bdfb38a0eacddfce. Report an issue: GitHub.