agalwood/Motrix · error · AppError

TaskCreateTorrentMetaFailed

TaskCreateTorrentMetaFailed

Error message

Failed to persist torrent metadata

What it means

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.

Source

Thrown at src/core/task/create-task-handler.ts:237

  const finalPath = path.join(effectiveSaveDir, finalName)
  const diskPath = toTempPath(finalPath)

  const taskId = newTaskId()
  // Anchor "now" early so the hook DTO's requestedAt and the persisted
  // task row share a clock — they are written in the same SQLite
  // transaction when plugin metadata is staged.
  const now = Date.now()

  // 2. Persist torrent bytes for the re-seed dance (BT with raw .torrent).
  let torrentMetaPath: string | null = null
  if (torrentBytes) {
    try {
      torrentMetaPath = await deps.torrentMetaStore.persist(
        taskId,
        torrentBytes
      )
    } catch (cause) {
      throw new AppError(
        ErrorCode.TaskCreateTorrentMetaFailed,
        'Failed to persist torrent metadata',
        cause
      )
    }
  }

  // 2.5. Pre-create the on-disk slot before handing off to aria2.
  // Both task families keep a `.motrix` suffix during download, but
  // `diskPath` means different things:
  //   - BT/Magnet: `diskPath` IS a container directory aria2 populates
  //     (`dir = diskPath`, `out` dropped). Pre-creating it also
  //     guarantees `aria2.addTorrent`'s metadata write
  //     (`<diskPath>/<sha1>.torrent`) succeeds at add time. Without
  //     this dir, the save fails silently; the resulting task gets a
  //     data-only `MetadataInfo` and the sqlite3 `task` row is never
  //     written, so the next pause hits a FOREIGN KEY violation when
  //     `task_progress` is upserted.

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Verify the metadataDir exists, is writable, and has free space before create-task is invoked.
  2. Move the metadata store to a stable location that survives reboots.
  3. Check permissions of the metadataDir for the app's user account.
  4. Whitelist the metadataDir in antivirus/defender exclusions.

Example fix

// before
try {
  torrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)
} catch (cause) {
  throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'Failed to persist torrent metadata', cause)
}

// after — pre-flight the store and surface the FS reason
await deps.fs.access(deps.torrentMetaStore.dir, deps.fs.constants.W_OK)
  .catch(() => { throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'metadataDir not writable') })
torrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)
Defensive patterns

Strategy: validation

Validate before calling

async function storeWritable(fs, dir) {
  try { await fs.access(dir, fs.constants.W_OK); return true } catch { return false }
}
if (!(await storeWritable(deps.fs, deps.torrentMetaStore.dir))) {
  // surface 'metadataDir not writable' before create
}

Type guard

function isTorrentMetaPersistFailed(e) { return e instanceof AppError && e.code === ErrorCode.TaskCreateTorrentMetaFailed }

Try / catch

try {
  torrentMetaPath = await deps.torrentMetaStore.persist(taskId, torrentBytes)
} catch (cause) {
  throw new AppError(ErrorCode.TaskCreateTorrentMetaFailed, 'Failed to persist torrent metadata', cause)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/33f006c550cf2792. Report an issue: GitHub.