agalwood/Motrix · error · RangeError

kind is not a legal history event kind

Error message

kind is not a legal history event kind

What it means

`validateHistoryEventInput` requires `input.kind` to be one of the `TaskHistoryEventKind` enum values: `added`, `started`, `paused`, `resumed`, `stage_changed`, `completed`, `failed`, `observed_state`. The set is built once from `Object.values(...)`. A non-matching kind throws this `RangeError` before any state-machine checks run.

Source

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

    throw new RangeError(
      `${label} JSON must not exceed ${maxJsonLength} characters`
    )
  }
}

export function validateHistoryEventInput(input: TaskHistoryEventInput): void {
  assertTaskId(input.taskId)
  assertPositiveSafeInteger(input.eventOrdinal, 'eventOrdinal')
  assertBoundedText(input.eventKey, 'eventKey', MAX_EVENT_KEY_LENGTH)
  assertBoundedText(
    input.runtimeGeneration,
    'runtimeGeneration',
    MAX_EVENT_KEY_LENGTH
  )
  assertPositiveSafeInteger(input.occurredAt, 'occurredAt')
  assertNonNegativeSafeInteger(input.occurredMonotonicMs, 'occurredMonotonicMs')
  if (!EVENT_KIND_VALUES.has(input.kind)) {
    throw new RangeError('kind is not a legal history event kind')
  }
  if (!ACCURACY_VALUES.has(input.accuracy)) {
    throw new RangeError('accuracy is not legal')
  }
  if (!DELIVERY_VALUES.has(input.delivery)) {
    throw new RangeError('delivery is not legal')
  }
  assertStatus(input.fromStatus, 'fromStatus')
  assertStatus(input.toStatus, 'toStatus')
  if (input.toStatus === null) {
    throw new RangeError('toStatus is required')
  }
  assertBoundedText(input.errorCode, 'errorCode', MAX_ERROR_CODE_LENGTH)
  assertBoundedText(
    input.errorMessage,
    'errorMessage',
    MAX_ERROR_MESSAGE_LENGTH
  )

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Map the upstream event type through a lookup table that drops unmappable events.
  2. Reference `TaskHistoryEventKind.X` instead of literals so renames are tracked by the compiler.
  3. Quarantine events whose kind cannot be mapped and surface them in telemetry — do not submit them.
  4. Add a migration that backfills any renamed enum values in durable storage.

Example fix

// before
input.kind = engineEventType  // 'begin' — not in enum
// after
const KIND_MAP: Record<string, TaskHistoryEventKind> = {
  begin: TaskHistoryEventKind.Started,
}
input.kind = KIND_MAP[engineEventType]
if (input.kind === undefined) return
Defensive patterns

Strategy: type-guard

Validate before calling

import { TaskHistoryEventKind } from '@shared/types/task-inspector-activity'
const KIND_VALUES = new Set(Object.values(TaskHistoryEventKind))
function coerceKind(v: unknown): TaskHistoryEventKind | null {
  return typeof v === 'string' && KIND_VALUES.has(v) ? (v as TaskHistoryEventKind) : null
}

Type guard

import { TaskHistoryEventKind } from '@shared/types/task-inspector-activity'
const KIND_VALUES = new Set(Object.values(TaskHistoryEventKind))
function isHistoryKind(v: unknown): v is TaskHistoryEventKind {
  return typeof v === 'string' && KIND_VALUES.has(v)
}

Prevention

When it happens

Trigger: Submitting an event with a kind string from a foreign vocabulary, an old identifier (`'stop'` instead of `'paused'`), or accidentally passing the raw engine event type. Also fires when kind is `undefined`/empty after a bad deserialization.

Common situations: Bridging an external download engine's event names; replaying events from an older DB schema; tests using literals like `'started'` that later got renamed.

Related errors


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