{"record":{"id":"8f61890590b14268","repo":"agalwood/Motrix","slug":"completed-must-end-in-completed","errorCode":null,"errorMessage":"Completed must end in completed","messagePattern":"Completed must end in completed","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"src/core/inspector-activity/validators.ts","lineNumber":231,"sourceCode":"      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')\n      }\n      break\n    case TaskHistoryEventKind.ObservedState:\n      if (input.accuracy !== TaskHistoryAccuracy.Recovered) {\n        throw new RangeError('Observed state must be recovered')\n      }\n      break\n    case TaskHistoryEventKind.StageChanged:\n      break\n  }\n}\n\nexport function normalizeTransferSamples(","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/inspector-activity/validators.ts#L213-L249","documentation":"For `TaskHistoryEventKind.Completed`, `toStatus` must be exactly `TaskStatus.Completed`. Throws this `RangeError` otherwise — a Completed event that lands anywhere else (e.g. `seeding`, `error`) is a logical contradiction. A BT task that finishes downloading but keeps seeding should use `StageChanged`, not `Completed`.","triggerScenarios":"Emitting Completed when the task transitions to `seeding` (BT post-download) or `error`; usually because the producer fired Completed on engine `complete` regardless of the resolved target.","commonSituations":"BT workflows where 'download finished' means 'now seeding'; engine signals that map to error rather than completion; tests reusing Completed for any terminal transition.","solutions":["Reserve Completed for `toStatus === TaskStatus.Completed`.","Use StageChanged for the download->seeding transition.","Use Failed for terminal errors."],"exampleFix":"// before\nemit({ kind: TaskHistoryEventKind.Completed, fromStatus: TaskStatus.Downloading, toStatus: TaskStatus.Seeding })\n// after\nemit({ kind: TaskHistoryEventKind.StageChanged, fromStatus: TaskStatus.Downloading, toStatus: TaskStatus.Seeding })","handlingStrategy":"validation","validationCode":"if (newStatus === TaskStatus.Completed) emit({ kind: TaskHistoryEventKind.Completed, /* ... */ })\nelse if (newStatus === TaskStatus.Error) emit({ kind: TaskHistoryEventKind.Failed, /* ... */ })\nelse emit({ kind: TaskHistoryEventKind.StageChanged, /* ... */ })","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Map each terminal target status to its own event kind.","Treat download->seeding as StageChanged, not Completed."],"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"}