agalwood/Motrix · error · RangeError

${label} is not a legal task status

Error message

${label} is not a legal task status

What it means

`assertStatus` allows `null` (status not applicable) or any value present in the `TaskStatus` enum: `queued`, `fetching_metadata`, `metadata_ready`, `downloading`, `finalizing`, `seeding`, `paused`, `completed`, `error`, `removed`. It throws this `RangeError` when the value is a non-null string outside that set. This protects the SQLite `status` column's CHECK constraint and the downstream state-machine invariants. `label` is `fromStatus` or `toStatus`.

Source

Thrown at src/core/inspector-activity/validators.ts:134

  value: string | null,
  label: string,
  maxLength: number
): void {
  if (value === null) return
  if (
    typeof value !== 'string' ||
    value.length === 0 ||
    value.length > maxLength
  ) {
    throw new RangeError(
      `${label} must contain between 1 and ${maxLength} characters`
    )
  }
}

function assertStatus(value: TaskStatus | null, label: string): void {
  if (value !== null && !STATUS_VALUES.has(value)) {
    throw new RangeError(`${label} is not a legal task status`)
  }
}

function assertBoundedDetailParams(
  value: Record<string, string> | null,
  label: string,
  maxJsonLength: number
): void {
  if (value === null) return
  if (
    typeof value !== 'object' ||
    Array.isArray(value) ||
    Object.values(value).some((entry) => typeof entry !== 'string')
  ) {
    throw new RangeError(`${label} must be a flat string record`)
  }
  const serialized = JSON.stringify(value)
  if (serialized.length > maxJsonLength) {

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Map the incoming status through a translation table whose keys cover every foreign value, falling through to a known default.
  2. If the source is local, reference `TaskStatus.X` instead of a string literal so the compiler tracks renames.
  3. Drop or quarantine events with unmappable statuses rather than passing them to the validator.
  4. Re-run any migration that backfills old status identifiers.

Example fix

// before
input.fromStatus = engineState  // 'running' — not in TaskStatus
// after
const FROM_ENGINE: Record<string, TaskStatus> = {
  running: TaskStatus.Downloading,
  stopped: TaskStatus.Paused,
}
input.fromStatus = FROM_ENGINE[engineState] ?? null
Defensive patterns

Strategy: type-guard

Validate before calling

import { TaskStatus } from '@shared/types/task'
const STATUS_VALUES = new Set(Object.values(TaskStatus))
function coerceStatus(v: unknown): TaskStatus | null {
  return typeof v === 'string' && STATUS_VALUES.has(v) ? (v as TaskStatus) : null
}

Type guard

import { TaskStatus } from '@shared/types/task'
const STATUS_VALUES = new Set(Object.values(TaskStatus))
function isTaskStatus(v: unknown): v is TaskStatus {
  return typeof v === 'string' && STATUS_VALUES.has(v)
}

Prevention

When it happens

Trigger: Submitting a `TaskHistoryEventInput` whose `fromStatus`/`toStatus` carries a value outside the enum — e.g. an old serialized name (`'fetching'`), a foreign status (`'running'`), or a numeric status code that should have been translated. Surfaces when replaying history from an older schema or bridging an external engine's status vocabulary.

Common situations: Version drift: an event written under a previous release where a status had a different identifier; consuming a third-party engine's status enum directly; tests with hardcoded string literals that drift from the enum.

Related errors


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