{"record":{"id":"33f006c550cf2792","repo":"agalwood/Motrix","slug":"taskcreatetorrentmetafailed","errorCode":"TaskCreateTorrentMetaFailed","errorMessage":"Failed to persist torrent metadata","messagePattern":"Failed to persist torrent metadata","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/task/create-task-handler.ts","lineNumber":237,"sourceCode":"  const finalPath = path.join(effectiveSaveDir, finalName)\n  const diskPath = toTempPath(finalPath)\n\n  const taskId = newTaskId()\n  // Anchor \"now\" early so the hook DTO's requestedAt and the persisted\n  // task row share a clock — they are written in the same SQLite\n  // transaction when plugin metadata is staged.\n  const now = Date.now()\n\n  // 2. Persist torrent bytes for the re-seed dance (BT with raw .torrent).\n  let torrentMetaPath: string | null = null\n  if (torrentBytes) {\n    try {\n      torrentMetaPath = await deps.torrentMetaStore.persist(\n        taskId,\n        torrentBytes\n      )\n    } catch (cause) {\n      throw new AppError(\n        ErrorCode.TaskCreateTorrentMetaFailed,\n        'Failed to persist torrent metadata',\n        cause\n      )\n    }\n  }\n\n  // 2.5. Pre-create the on-disk slot before handing off to aria2.\n  // Both task families keep a `.motrix` suffix during download, but\n  // `diskPath` means different things:\n  //   - BT/Magnet: `diskPath` IS a container directory aria2 populates\n  //     (`dir = diskPath`, `out` dropped). Pre-creating it also\n  //     guarantees `aria2.addTorrent`'s metadata write\n  //     (`<diskPath>/<sha1>.torrent`) succeeds at add time. Without\n  //     this dir, the save fails silently; the resulting task gets a\n  //     data-only `MetadataInfo` and the sqlite3 `task` row is never\n  //     written, so the next pause hits a FOREIGN KEY violation when\n  //     `task_progress` is upserted.","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/task/create-task-handler.ts#L219-L255","documentation":"Thrown by create-task-handler.ts:237 when `deps.torrentMetaStore.persist(taskId, torrentBytes)` rejects while saving the `.torrent` sidecar for a BT task created with raw torrent bytes. Without the persisted sidecar the task cannot be re-seeded later, so creation is aborted.","triggerScenarios":"The metadataDir is on a full or read-only filesystem; permission denied writing the sidecar; the path is invalid (too long, illegal chars); the metadata store volume was unmounted; a transient I/O error mid-write.","commonSituations":"Default metadataDir points at a temp/external volume that is gone; disk full from large downloads; permissions tightened by an OS update; antivirus blocking writes of `.torrent` files.","solutions":["Verify the metadataDir exists, is writable, and has free space before create-task is invoked.","Move the metadata store to a stable location that survives reboots.","Check permissions of the metadataDir for the app's user account.","Whitelist the metadataDir in antivirus/defender exclusions."],"exampleFix":"// before\ntry {\n  torrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)\n} catch (cause) {\n  throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'Failed to persist torrent metadata', cause)\n}\n\n// after — pre-flight the store and surface the FS reason\nawait deps.fs.access(deps.torrentMetaStore.dir, deps.fs.constants.W_OK)\n  .catch(() => { throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'metadataDir not writable') })\ntorrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)","handlingStrategy":"validation","validationCode":"async function storeWritable(fs, dir) {\n  try { await fs.access(dir, fs.constants.W_OK); return true } catch { return false }\n}\nif (!(await storeWritable(deps.fs, deps.torrentMetaStore.dir))) {\n  // surface 'metadataDir not writable' before create\n}","typeGuard":"function isTorrentMetaPersistFailed(e) { return e instanceof AppError && e.code === ErrorCode.TaskCreateTorrentMetaFailed }","tryCatchPattern":"try {\n  torrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)\n} catch (cause) {\n  throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'Failed to persist torrent metadata', cause)\n}","preventionTips":["Pre-flight that the metadataDir exists and is writable before create.","Use a stable, non-temp metadataDir that survives reboots.","Whitelist the metadataDir in antivirus exclusions.","Monitor free space on the metadataDir volume."],"tags":["create","bittorrent","metadata","filesystem","io"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}