{"record":{"id":"f432cb0947028f14","repo":"agalwood/Motrix","slug":"magnetcleanuppending","errorCode":"MagnetCleanupPending","errorMessage":"aria2 cleanup for a failed magnet swap is still pending for task ${taskId}; please retry confirmation shortly.","messagePattern":"aria2 cleanup for a failed magnet swap is still pending for task (.+?); please retry confirmation shortly\\.","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/torrent/swap-magnet-metadata-for-bt.ts","lineNumber":137,"sourceCode":"    torrentMetaStore,\n    publishTaskUpdate,\n    publishTaskUpdateNow,\n    recordTransition,\n  } = deps\n\n  const existing = db.getTask(taskId)\n  if (!existing) {\n    throw new AppError(\n      ErrorCode.TaskNotFound,\n      `task ${taskId} not found for magnet metadata swap`\n    )\n  }\n\n  if (\n    magnetTracker.hasPendingSwapCleanup(taskId) ||\n    existing.instances.some(isMagnetCleanupTombstoneHidden)\n  ) {\n    throw new AppError(\n      ErrorCode.MagnetCleanupPending,\n      `aria2 cleanup for a failed magnet swap is still pending for ` +\n        `task ${taskId}; please retry confirmation shortly.`\n    )\n  }\n  const previousMetadataInstance =\n    existing.instances.length === 1 ? existing.instances[0] : undefined\n  if (\n    existing.task.taskType !== TaskType.Magnet ||\n    existing.task.aggStatus !== TaskStatus.MetadataReady ||\n    previousMetadataInstance?.phase !==\n      TaskInstancePhase.MagnetMetadataResolution ||\n    previousMetadataInstance.status !== TaskStatus.MetadataReady\n  ) {\n    throw new AppError(\n      ErrorCode.InvalidSelection,\n      `task ${taskId} is not a MetadataReady magnet selection`\n    )","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/torrent/swap-magnet-metadata-for-bt.ts#L119-L155","documentation":"Thrown by swap-magnet-metadata-for-bt.ts:137 when either `magnetTracker.hasPendingSwapCleanup(taskId)` is true or one of the task's instances is a hidden magnet-cleanup tombstone. A previous swap failed and aria2 cleanup of the orphaned GID is still quarantined, so starting a new swap now would create a sibling GID while the old one is still being torn down.","triggerScenarios":"User retries the swap immediately after a previous failed swap whose aria2 RPC cleanup returned 'quarantined'; a transient RPC failure left the swap cleanup tombstone in place; the magnet-cleanup worker has not yet finished reaping the old GID.","commonSituations":"Impatient retry after a failed swap; aria2 briefly unreachable (RPC timeout) during cleanup; rapid double-click of the confirm button.","solutions":["Wait a short interval and retry — the cleanup worker reaps the quarantine and the tombstone clears automatically.","Confirm aria2 is healthy and reachable so the pending cleanup can complete.","Surface a user-facing 'cleanup in progress, retry shortly' message instead of a raw error.","Disable the confirm button while `magnetTracker.hasPendingSwapCleanup(taskId)` is true."],"exampleFix":"// before\nif (magnetTracker.hasPendingSwapCleanup(taskId) || existing.instances.some(isMagnetCleanupTombstoneHidden)) {\n  throw new AppError(ErrorCode.MagnetCleanupPending, `aria2 cleanup for a failed magnet swap is still pending for task ${taskId}; please retry confirmation shortly.`)\n}\n\n// after — UI-driven backoff retry\nasync function swapWithBackoff(taskId, deps) {\n  for (let attempt = 0; attempt < 5; attempt++) {\n    try { return await swapMagnetMetadata(taskId, deps) }\n    catch (e) {\n      if (e instanceof AppError && e.code === ErrorCode.MagnetCleanupPending && attempt < 4) {\n        await delay(1000 * (attempt + 1)); continue\n      }\n      throw e\n    }\n  }\n}","handlingStrategy":"retry","validationCode":"function swapCleanupDone(magnetTracker, existing, taskId) {\n  return !magnetTracker.hasPendingSwapCleanup(taskId)\n    && !existing.instances.some(isMagnetCleanupTombstoneHidden)\n}","typeGuard":"function isSwapCleanupPending(e) { return e instanceof AppError && e.code === ErrorCode.MagnetCleanupPending && /failed magnet swap/.test(e.message) }","tryCatchPattern":"for (let attempt = 0; attempt < 5; attempt++) {\n  try { return await swapMagnetMetadata(taskId, deps) }\n  catch (e) {\n    if (e instanceof AppError && e.code === ErrorCode.MagnetCleanupPending && attempt < 4) {\n      await delay(1000 * (attempt + 1)); continue\n    }\n    throw e\n  }\n}","preventionTips":["Wait a short interval and retry — the cleanup worker drains the quarantine.","Disable the confirm button while hasPendingSwapCleanup(taskId) is true.","Confirm aria2 is healthy so cleanup can complete.","Surface a user-facing 'cleanup in progress, retry shortly' message."],"tags":["magnet","swap","aria2","cleanup","retryable","concurrency"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}