agalwood/Motrix · error · RangeError

${label} must be a flat string record

Error message

${label} must be a flat string record

What it means

`assertBoundedDetailParams` accepts `null` or a plain object whose every value is a string — `Record<string,string>`. It throws this `RangeError` if the value is not an object, is an array, or contains any non-string value. The shape mirrors i18n interpolation params (`{file: 'movie.mp4', host: 'example.com'}`). Used only for `errorDetailParams`.

Source

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

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) {
    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
  )

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Stringify every param value before assignment: `Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, String(v)]))`.
  2. Filter out non-scalar values or stringify nested objects with `JSON.stringify`.
  3. Drop the field entirely (`null`) when params are not primitive strings.
  4. Validate at the IPC edge with a schema that enforces `Record<string,string>`.

Example fix

// before
event.errorDetailParams = rawEngineParams  // {file: {name: 'x'}, size: 1024}
// after
event.errorDetailParams = Object.fromEntries(
  Object.entries(rawEngineParams).map(([k, v]) => [k, typeof v === 'string' ? v : JSON.stringify(v)])
)
Defensive patterns

Strategy: type-guard

Validate before calling

function flattenStringRecord(v: unknown): Record<string, string> | null {
  if (v == null || typeof v !== 'object' || Array.isArray(v)) return null
  const out: Record<string, string> = {}
  for (const [k, val] of Object.entries(v as Record<string, unknown>))
    out[k] = typeof val === 'string' ? val : JSON.stringify(val)
  return out
}

Type guard

function isFlatStringRecord(v: unknown): v is Record<string, string> {
  return typeof v === 'object' && v !== null && !Array.isArray(v) &&
    Object.values(v).every((x) => typeof x === 'string')
}

Prevention

When it happens

Trigger: Passing `errorDetailParams` with nested objects (`{file: {name: '...'}}`), numbers (`{count: 5}`), arrays, or as a raw string instead of a record. Common at sites that forward an arbitrary engine error payload into the event without normalization.

Common situations: Engine error bodies whose params arrive as JSON with mixed types; copy-pasting a `Record<string, unknown>` into the field; tests that pass `{ size: 1024 }` directly.

Related errors


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