agalwood/Motrix · error · Error
aria2 returned gid ${actualGid} instead of reserved gid ${re
Error message
aria2 returned gid ${actualGid} instead of reserved gid ${requestedGid} What it means
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.
Source
Thrown at src/core/engine/aria2/aria2-adapter.ts:445
}
if (params.ulLimit !== undefined) {
opts['max-upload-limit'] = `${params.ulLimit}K`
}
if (params.extraEngineOptions) {
for (const [k, v] of Object.entries(params.extraEngineOptions)) {
opts[k] = v as string
}
}
if (requestedGid !== undefined) {
opts.gid = requestedGid
}
const b64 = Buffer.from(params.metadata).toString('base64')
const actualGid = await this.rpc.addTorrent(b64, [], opts)
if (
requestedGid !== undefined &&
actualGid.toLowerCase() !== requestedGid.toLowerCase()
) {
throw new Error(
`aria2 returned gid ${actualGid} instead of reserved gid ${requestedGid}`
)
}
return params.gid ?? actualGid
}
/**
* Whether a "GID is not found" reply from this engine can be trusted as
* durably absent. On a sqlite3-persistence engine that predates
* 1.37.0-motrix.3, that wording also covers evicted-but-persisted gids and
* FAILED persistent deletes — so treating it as removed would let callers
* erase local records while the durable engine row survives. Without
* persistence there is no durable row a not-found could be hiding, so it is
* always safe. Shared by the single and batch remove paths.
*/
private trustsNotFound(): boolean {
return (
!this.featureReport.hasSqlitePersistence ||View on GitHub (pinned to 1a708ee577)
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
Defensive patterns
Strategy: try-catch
Try / catch
try {
return await adapter.addTorrent(params)
} catch (e) {
if (e instanceof Error && /aria2 returned gid .* instead of reserved gid/.test(e.message)) {
// The requested GID was taken — generate a fresh one and retry
params.gid = crypto.randomBytes(8).toString('hex')
return await adapter.addTorrent(params)
}
throw e
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- aria2 addTorrent gid must contain exactly 16 hexadecimal cha
- ${callerName} reserved gid must contain exactly 16 hexadecim
- MagnetCleanupPending
- EngineProtocolError
- EngineStartFailed
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/b0d960c7ab86f607.
Report an issue: GitHub.