{"record":{"id":"bdfb38a0eacddfce","repo":"AlistGo/alist","slug":"failed-get-torrent-id","errorCode":null,"errorMessage":"failed get torrent ID","messagePattern":"failed get torrent ID","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/offline_download/transmission/client.go","lineNumber":115,"sourceCode":"\t\t}\n\t\t// Flush last bytes\n\t\tif err = encoder.Close(); err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"can't flush last bytes of the base64 encoder\")\n\t\t}\n\t\t// Get the string form\n\t\tb64 := buffer.String()\n\t\trpcPayload.MetaInfo = &b64\n\t} else { // magnet uri\n\t\trpcPayload.Filename = &args.Url\n\t}\n\n\ttorrent, err := t.client.TorrentAdd(context.TODO(), rpcPayload)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif torrent.ID == nil {\n\t\treturn \"\", fmt.Errorf(\"failed get torrent ID\")\n\t}\n\tgid := strconv.FormatInt(*torrent.ID, 10)\n\treturn gid, nil\n}\n\nfunc (t *Transmission) Remove(task *tool.DownloadTask) error {\n\tgid, err := strconv.ParseInt(task.GID, 10, 64)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = t.client.TorrentRemove(context.TODO(), transmissionrpc.TorrentRemovePayload{\n\t\tIDs:             []int64{gid},\n\t\tDeleteLocalData: false,\n\t})\n\treturn err\n}\n\nfunc (t *Transmission) Status(task *tool.DownloadTask) (*tool.Status, error) {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/offline_download/transmission/client.go#L97-L133","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check whether the torrent already exists (by hash) before adding, and reuse its id","If already added, query TorrentGetAllFor / get by hash to obtain the real id","Upgrade the transmissionrpc library — newer versions normalize the duplicate response","As a workaround, remove the duplicate torrent first, then re-add"],"exampleFix":"// before\nif torrent.ID == nil {\n    return \"\", fmt.Errorf(\"failed get torrent ID\")\n}\n\n// after: fall back to hash lookup for the duplicate case\nif torrent.ID == nil && torrent.HashString != nil {\n    infos, err := t.client.TorrentGetAllFor(ctx, nil)\n    if err == nil {\n        for _, i := range infos {\n            if i.HashString != nil && *i.HashString == *torrent.HashString {\n                return strconv.FormatInt(*i.ID, 10), nil\n            }\n        }\n    }\n}\nif torrent.ID == nil {\n    return \"\", fmt.Errorf(\"failed get torrent ID\")\n}","handlingStrategy":"validation","validationCode":"torrent, err := t.client.TorrentAdd(context.TODO(), rpcPayload)\nif err != nil {\n    return \"\", err\n}\nif torrent.ID == nil {\n    // likely a duplicate: resolve the existing torrent by hash instead of failing\n    if torrent.HashString != nil {\n        if infos, ierr := t.client.TorrentGetAllFor(context.TODO(), nil); ierr == nil {\n            for _, i := range infos {\n                if i.HashString != nil && *i.HashString == *torrent.HashString && i.ID != nil {\n                    return strconv.FormatInt(*i.ID, 10), nil\n                }\n            }\n        }\n    }\n    return \"\", fmt.Errorf(\"failed get torrent ID\")\n}","typeGuard":"func hasTorrentID(t *transmissionrpc.Torrent) bool {\n    return t != nil && t.ID != nil\n}","tryCatchPattern":"gid, err := t.AddURL(args)\nif err != nil && strings.Contains(err.Error(), \"failed get torrent ID\") {\n    // torrent probably already exists: ask the user or dedupe by hash\n    return errorWithHint(err, \"torrent may already exist in transmission; remove it or reuse its id\")\n}","preventionTips":["Check for an existing torrent by hash before calling TorrentAdd","Never blind-retry TorrentAdd on this error — it deepens the duplicate","Keep the transmissionrpc library current so duplicate responses are normalized"],"tags":["transmission","torrent-add","duplicate","nil-pointer"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}