{"record":{"id":"b0d960c7ab86f607","repo":"agalwood/Motrix","slug":"aria2-returned-gid-actualgid-instead-of-reserve","errorCode":null,"errorMessage":"aria2 returned gid ${actualGid} instead of reserved gid ${requestedGid}","messagePattern":"aria2 returned gid (.+?) instead of reserved gid (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/core/engine/aria2/aria2-adapter.ts","lineNumber":445,"sourceCode":"    }\n    if (params.ulLimit !== undefined) {\n      opts['max-upload-limit'] = `${params.ulLimit}K`\n    }\n    if (params.extraEngineOptions) {\n      for (const [k, v] of Object.entries(params.extraEngineOptions)) {\n        opts[k] = v as string\n      }\n    }\n    if (requestedGid !== undefined) {\n      opts.gid = requestedGid\n    }\n    const b64 = Buffer.from(params.metadata).toString('base64')\n    const actualGid = await this.rpc.addTorrent(b64, [], opts)\n    if (\n      requestedGid !== undefined &&\n      actualGid.toLowerCase() !== requestedGid.toLowerCase()\n    ) {\n      throw new Error(\n        `aria2 returned gid ${actualGid} instead of reserved gid ${requestedGid}`\n      )\n    }\n    return params.gid ?? actualGid\n  }\n\n  /**\n   * Whether a \"GID is not found\" reply from this engine can be trusted as\n   * durably absent. On a sqlite3-persistence engine that predates\n   * 1.37.0-motrix.3, that wording also covers evicted-but-persisted gids and\n   * FAILED persistent deletes — so treating it as removed would let callers\n   * erase local records while the durable engine row survives. Without\n   * persistence there is no durable row a not-found could be hiding, so it is\n   * always safe. Shared by the single and batch remove paths.\n   */\n  private trustsNotFound(): boolean {\n    return (\n      !this.featureReport.hasSqlitePersistence ||","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/engine/aria2/aria2-adapter.ts#L427-L463","documentation":"Thrown as a plain Error by Aria2Adapter.addTorrent after the RPC call aria2.addTorrent returns a gid that does not match the requestedGid the adapter sent. The adapter passes opts.gid = requestedGid to the engine; if aria2 returns a different gid (case-insensitive comparison), it means the requested GID was already in use or otherwise rejected by aria2 internally. This is a consistency assertion, not a pre-validation.","triggerScenarios":"addTorrent sends a specific gid in opts; aria2.addTorrent succeeds but returns an actualGid that differs (case-insensitive) from the reserved requestedGid. The most common cause is the gid was already assigned to an existing (non-removed) download in aria2's session, so aria2 silently allocated a new one.","commonSituations":"Caller tries to reserve a GID that is still active in aria2 from a previous task that wasn't cleaned up; a stale session file causes aria2 to retain the GID; concurrent calls race to claim the same GID; the GID was evicted from aria2's in-memory window but persists in sqlite3 history on the Motrix fork.","solutions":["Do not assume a specific GID is available — if you need a reserved GID, generate a fresh random one each time and handle the mismatch by retrying with a new GID","Ensure prior tasks using that GID are fully removed (adapter.removeTask + removeDownloadResult) before re-reserving it","Let aria2 auto-assign the GID (omit params.gid) and use the returned actualGid for all subsequent operations","Catch this Error and fall back to using actualGid from the return value if the reservation is not strictly required"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await adapter.addTorrent(params)\n} catch (e) {\n  if (e instanceof Error && /aria2 returned gid .* instead of reserved gid/.test(e.message)) {\n    // The requested GID was taken — generate a fresh one and retry\n    params.gid = crypto.randomBytes(8).toString('hex')\n    return await adapter.addTorrent(params)\n  }\n  throw e\n}","preventionTips":["Do not reuse GIDs across tasks — generate a fresh random GID for each addTorrent call","Fully remove prior tasks (removeTask + removeDownloadResult) before re-reserving a GID","Prefer letting aria2 auto-assign GIDs (omit params.gid) unless you need a specific one for correlation"],"tags":["aria2","torrent","gid","assertion","concurrency"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}