{"record":{"id":"eb215cce06f1188f","repo":"agalwood/Motrix","slug":"paused-must-transition-from-active-to-paused","errorCode":null,"errorMessage":"Paused must transition from active to paused","messagePattern":"Paused must transition from active to paused","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"src/core/inspector-activity/validators.ts","lineNumber":218,"sourceCode":"\n  switch (input.kind) {\n    case TaskHistoryEventKind.Added:\n      if (input.fromStatus !== null) {\n        throw new RangeError('Added must not have a fromStatus')\n      }\n      break\n    case TaskHistoryEventKind.Started:\n      if (!ACTIVE_RESUME_STATUSES.has(input.toStatus)) {\n        throw new RangeError('Started must enter an active status')\n      }\n      break\n    case TaskHistoryEventKind.Paused:\n      if (\n        input.fromStatus === null ||\n        !ACTIVE_RESUME_STATUSES.has(input.fromStatus) ||\n        input.toStatus !== TaskStatus.Paused\n      ) {\n        throw new RangeError('Paused must transition from active to paused')\n      }\n      break\n    case TaskHistoryEventKind.Resumed:\n      if (\n        input.fromStatus !== TaskStatus.Paused ||\n        !ACTIVE_RESUME_STATUSES.has(input.toStatus)\n      ) {\n        throw new RangeError('Resumed must transition from paused to active')\n      }\n      break\n    case TaskHistoryEventKind.Completed:\n      if (input.toStatus !== TaskStatus.Completed) {\n        throw new RangeError('Completed must end in completed')\n      }\n      break\n    case TaskHistoryEventKind.Failed:\n      if (input.toStatus !== TaskStatus.Error) {\n        throw new RangeError('Failed must end in error')","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/inspector-activity/validators.ts#L200-L236","documentation":"For `TaskHistoryEventKind.Paused`, all three must hold: `fromStatus` is non-null, `fromStatus` is in `ACTIVE_RESUME_STATUSES` (`fetching_metadata`, `downloading`, `finalizing`, `seeding`), and `toStatus === TaskStatus.Paused`. Throws this `RangeError` otherwise — you cannot pause from an already-paused, terminal, or queued state.","triggerScenarios":"A Paused event whose `fromStatus` is itself `paused` (double-pause), `completed`/`error` (pausing a terminal task), or `null`, or whose `toStatus` is not `paused` — typically the producer forwarded an engine pause notification without checking the prior status.","commonSituations":"Engine-level pause/idle signals arriving after the task already transitioned terminal; replaying events out of order; tests that don't set fromStatus.","solutions":["Only emit Paused when the prior status is active and the new status is paused; otherwise emit the matching kind (or drop the event).","Track the persisted status and compare before emitting.","Quarantine out-of-order events during recovery."],"exampleFix":"// before\nemit({ kind: TaskHistoryEventKind.Paused, fromStatus: TaskStatus.Paused, toStatus: TaskStatus.Paused })\n// after\nif (currentStatus === TaskStatus.Paused) return  // already paused, no-op\nemit({ kind: TaskHistoryEventKind.Paused, fromStatus: currentStatus, toStatus: TaskStatus.Paused })","handlingStrategy":"validation","validationCode":"const ACTIVE = new Set([TaskStatus.FetchingMetadata, TaskStatus.Downloading, TaskStatus.Finalizing, TaskStatus.Seeding])\nfunction canPause(from: TaskStatus | null): boolean {\n  return from !== null && ACTIVE.has(from)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Emit Paused only from a known-active prior status.","Drop no-op pauses (fromStatus already Paused).","Replay events in ordinal order during recovery."],"tags":["validation","range-error","state-machine","events"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}