{"record":{"id":"5eae35471d0fce56","repo":"agalwood/Motrix","slug":"invalidselection","errorCode":"InvalidSelection","errorMessage":"task ${taskId} is not a MetadataReady magnet selection","messagePattern":"task (.+?) is not a MetadataReady magnet selection","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/torrent/swap-magnet-metadata-for-bt.ts","lineNumber":152,"sourceCode":"    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    )\n  }\n  const existingFiles = db.getTaskFiles(taskId)\n  const originalGraph = {\n    task: existing.task,\n    instances: existing.instances,\n    files: existingFiles,\n  }\n  const originalDownloadTask = taskRowToDownloadTask(\n    existing.task,\n    existing.instances\n  )\n  const originalOwner = taskManager.getById(taskId)\n  const torrentBytes = Buffer.from(base64, 'base64')\n\n  // Complete every fallible local preparation step before destructively","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/torrent/swap-magnet-metadata-for-bt.ts#L134-L170","documentation":"Thrown by swap-magnet-metadata-for-bt.ts:152 when the task is not a MetadataReady magnet selection: it requires `taskType === Magnet`, `aggStatus === MetadataReady`, exactly one instance, that instance's phase to be `MagnetMetadataResolution`, and its status to be `MetadataReady`. Any mismatch rejects the swap as an invalid selection.","triggerScenarios":"Swap requested on a task that already converted to bt_download; a magnet task still resolving metadata (not yet MetadataReady); a non-magnet task type; the metadata instance was already swapped or cancelled leaving multiple instances or a different phase.","commonSituations":"Stale confirmation window for a task that already moved state; double-swap attempt; user navigated away and back after a state change; IPC replay with outdated task state.","solutions":["Refresh task state in the renderer before showing the swap confirm dialog and only enable it while the task is MetadataReady magnet.","Subscribe to status/instance-change events and tear down the swap dialog when the task leaves the valid state.","Treat InvalidSelection as a benign user-facing 'selection no longer valid' notice rather than a hard error.","Ensure only one swap flow can run per task at a time."],"exampleFix":"// before\nif (existing.task.taskType !== TaskType.Magnet || existing.task.aggStatus !== TaskStatus.MetadataReady || previousMetadataInstance?.phase !== TaskInstancePhase.MagnetMetadataResolution || previousMetadataInstance?.status !== TaskStatus.MetadataReady) {\n  throw new AppError(ErrorCode.InvalidSelection, `task ${taskId} is not a MetadataReady magnet selection`)\n}\n\n// after — renderer-side guard so the throw is never reached\nconst swappable = task.taskType === 'magnet' && task.aggStatus === 'metadata-ready'\nui.setSwapEnabled(taskId, swappable)\nif (!swappable) return","handlingStrategy":"validation","validationCode":"import { TaskType, TaskStatus, TaskInstancePhase } from '@shared/types'\nfunction isSwappableMagnetSelection(task, instances) {\n  if (task.taskType !== TaskType.Magnet) return false\n  if (task.aggStatus !== TaskStatus.MetadataReady) return false\n  if (instances.length !== 1) return false\n  const inst = instances[0]\n  return inst.phase === TaskInstancePhase.MagnetMetadataResolution\n    && inst.status === TaskStatus.MetadataReady\n}","typeGuard":"function isInvalidSelection(e) { return e instanceof AppError && e.code === ErrorCode.InvalidSelection }","tryCatchPattern":"if (!isSwappableMagnetSelection(existing.task, existing.instances)) {\n  ui.notifyUser('selection_no_longer_valid')\n  return { outcome: 'noop', reason: 'invalid_selection' }\n}","preventionTips":["Refresh task state in the renderer before showing the swap dialog.","Tear down the swap dialog when the task leaves MetadataReady magnet state.","Treat InvalidSelection as a benign notice rather than a hard error.","Allow only one swap flow per task at a time."],"tags":["magnet","swap","validation","state","task-lifecycle"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}