agalwood/Motrix · error · AppError

TaskNotRetryable

TaskNotRetryable

Error message

Task ${taskId} cannot be retried: required inputs are unavailable

What it means

Thrown by re-add-task.ts:301 when `canRetry(task)` is true but `canRebuildTaskInputs(task)` is false. The task is formally in a retryable status, yet the engine dispatch cannot be reconstructed from the persisted record — e.g. a media task with no single re-addable handle, or a torrent-like task whose sidecar was dropped. The check rejects up front rather than failing mid-flow.

Source

Thrown at src/core/task/actions/re-add-task.ts:301

 */
async function reAddTaskUnderMutation(
  taskId: string,
  deps: ReAddTaskDeps
): Promise<void> {
  const task = getTaskOrWarn(deps, taskId, 'reAddTask')
  if (!task) return
  if (canRetry(task)) {
    // Retry additionally requires the engine dispatch to be reconstructable
    // from the task record (canReseed already implies this for its own path,
    // since it requires torrentMetaPath itself) — reject up front rather than
    // discovering mid-flow (e.g. a media task with no single re-addable
    // handle, or a torrent-like task with no persisted sidecar).
    if (!canRebuildTaskInputs(task)) {
      deps.log.warn(
        { taskId, status: task.status, type: task.type, kind: task.kind },
        'reAddTask: task inputs cannot be rebuilt'
      )
      throw new AppError(
        ErrorCode.TaskNotRetryable,
        `Task ${taskId} cannot be retried: required inputs are unavailable`
      )
    }
  } else if (!canReseed(task)) {
    deps.log.warn(
      { taskId, status: task.status, type: task.type },
      'reAddTask: task is not in a re-addable state'
    )
    return
  }
  // Resolve local prerequisites before touching the old engine GID or
  // installing a durable reservation. From the reservation onward, the only
  // fallible operation before publication is the engine dispatch itself.
  const torrentMetadata = await readBtMetadata(task, deps)
  let opts: EngineTaskOptions | null = null
  try {
    opts = await deps.adapter.getEngineTaskOptions(task.engineTaskId)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Gate the retry affordance on `canRebuildTaskInputs(task)` in the UI/controller so the button is disabled, not the throw hit.
  2. For torrent-like tasks, restore torrentMetaPath (re-persist the .torrent) before retry.
  3. For media/non-retryable kinds, surface a clear 'cannot be retried' status instead of attempting re-add.
  4. If the task genuinely should be retryable, fix the create path so its inputs are fully persisted.

Example fix

// before
if (canRetry(task)) {
  if (!canRebuildTaskInputs(task)) {
    throw new AppError(ErrorCode.TaskNotRetryable, `Task ${taskId} cannot be retried: required inputs are unavailable`)
  }
}

// after — UI layer disables the action upfront
const retryable = canRetry(task) && canRebuildTaskInputs(task)
if (!retryable) {
  ui.setRetryEnabled(taskId, false, 'Required inputs are no longer available')
  return
}
Defensive patterns

Strategy: validation

Validate before calling

import { canRetry, canRebuildTaskInputs } from '@core/task/shared'
function isRetryable(task) {
  return canRetry(task) && canRebuildTaskInputs(task)
}
if (!isRetryable(task)) {
  ui.setRetryEnabled(task.id, false, 'Required inputs unavailable')
}

Type guard

function isTaskNotRetryable(e) { return e instanceof AppError && e.code === ErrorCode.TaskNotRetryable }

Try / catch

if (canRetry(task) && !canRebuildTaskInputs(task)) {
  deps.log.warn({ taskId }, 'reAddTask: not retryable, inputs missing')
  return { retried: false, reason: 'inputs_unavailable' }
}

Prevention

When it happens

Trigger: Retry invoked on a media task that has no resumable single handle; a torrent-like task where the persisted sidecar/path is missing; a task type whose kind never has rebuildable inputs.

Common situations: UI retry button enabled purely on status without the capability check; partial create left a row that looks retryable; tasks of a kind that the engine cannot re-dispatch (live stream, unsupported protocol).

Related errors


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