{"record":{"id":"f9aaaac883e9b932","repo":"agalwood/Motrix","slug":"taskfinalizemetamissing-f9aaaa","errorCode":"TaskFinalizeMetaMissing","errorMessage":"Torrent metadata missing for task ${task.id}","messagePattern":"Torrent metadata missing for task (.+?)","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/task/actions/re-add-task.ts","lineNumber":75,"sourceCode":"    // not-found idempotence) is authoritative absence. forceRemove may have\n    // raced a row that was already stopped, so its failure alone must not\n    // retain a ghost owner.\n    return true\n  } catch (err) {\n    log.error(\n      { err: String(err), engineTaskId },\n      'reAddTask: failed to remove replacement result after add failure'\n    )\n    return false\n  }\n}\n\nasync function readBtMetadata(\n  task: DownloadTask,\n  deps: ReAddTaskDeps\n): Promise<Uint8Array> {\n  if (!task.torrentMetaPath) {\n    throw new AppError(\n      ErrorCode.TaskFinalizeMetaMissing,\n      `Torrent metadata missing for task ${task.id}`\n    )\n  }\n  return deps.torrentMetaStore.read(task.torrentMetaPath)\n}\n\n/**\n * Where aria2 should write this re-add.\n *\n * A Completed reseed points at `finalPath`: finalize already renamed the\n * container away from `.motrix`, and that is where the seedable content\n * lives (`diskPath` has been normalized to the same value anyway).\n *\n * A retry of a failed or removed download is the opposite case — finalize\n * never ran, so the partial content is still in the in-flight `.motrix`\n * container that `createTaskHandler` passed as `dir` at add time. Pointing\n * `checkIntegrity` at `finalPath` there would scan an empty directory and","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/task/actions/re-add-task.ts#L57-L93","documentation":"Thrown by `readBtMetadata` in re-add-task.ts:75 during the re-seed/retry path when `task.torrentMetaPath` is falsy. Re-adding a torrent-like task into aria2 requires the original `.torrent` bytes; without the persisted sidecar path the re-add cannot reconstruct engine inputs.","triggerScenarios":"User clicks retry on a stopped BT task that was created without persisted torrent bytes; a task imported via migration where torrentMetaPath was not backfilled; a re-add invoked on a task that originally only had an info-hash with no .torrent file.","commonSituations":"Tasks created in an older version before torrentMetaPath was mandatory; magnet tasks that never reached MetadataReady before being stopped; DB rows altered by maintenance scripts.","solutions":["Block retry in the UI when `canRebuildTaskInputs(task)` / `canReseed(task)` return false — surface the task as non-retryable instead of throwing.","If the original `.torrent` is recoverable elsewhere, persist it via `torrentMetaStore.persist(taskId, bytes)` then retry.","For migrated rows missing torrentMetaPath, mark them non-retryable and re-create from the source link instead.","Ensure all BT create paths persist torrentMetaPath before the task becomes retryable."],"exampleFix":"// before\nasync function readBtMetadata(task, deps) {\n  if (!task.torrentMetaPath) {\n    throw new AppError(ErrorCode.TaskFinalizeMetaMissing, `Torrent metadata missing for task ${task.id}`)\n  }\n  return deps.torrentMetaStore.read(task.torrentMetaPath)\n}\n\n// after — caller guards before invoking\nif (!canReseed(task)) {\n  deps.log.warn({ taskId: task.id }, 'reAddTask: skipping reseed, no torrentMetaPath')\n  return false\n}","handlingStrategy":"validation","validationCode":"// Guard at the re-add entry before calling readBtMetadata\nimport { canReseed } from '@core/task/shared'\nif (!canReseed(task)) {\n  deps.log.warn({ taskId: task.id }, 'reAddTask: skipping reseed, torrentMetaPath missing')\n  return false\n}","typeGuard":"function isReAddMetaMissing(e) { return e instanceof AppError && e.code === ErrorCode.TaskFinalizeMetaMissing && /Torrent metadata missing for task/.test(e.message) }","tryCatchPattern":"if (!task.torrentMetaPath) {\n  // mark non-retryable instead of throwing\n  deps.log.warn({ taskId: task.id }, 'reAddTask: no torrentMetaPath')\n  return false\n}","preventionTips":["Gate the retry UI on canReseed(task) so the throw is never reached.","Persist torrentMetaPath during create for all BT tasks.","Backfill torrentMetaPath for migrated rows where possible.","Surface non-retryable tasks clearly rather than letting users hit the throw."],"tags":["bittorrent","re-add","retry","metadata","state"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}