pydantic/monty · error · TypeError

MontyFileHandle mode must be a string

Error message

MontyFileHandle mode must be a string

What it means

pushFileHandle requires the file-handle marker's `mode` (the file open mode such as 'r', 'w', 'rb') to be a string; this TypeError is thrown for numbers, null, undefined, or any non-string. The mode is further normalized by canonicalFileMode before crossing the wire, but the typeof check happens first.

Source

Thrown at crates/monty-js/ts/worker/value.ts:249

    attrs: pushPairs(attrPairs, nodes),
  }
}

/** A canonical uuid string is required for identities crossing the wire. */
function uuidString(value: unknown, what: string): string {
  if (
    typeof value !== 'string' ||
    !/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(value)
  ) {
    throw new TypeError(`${what} must be a canonical uuid string`)
  }
  return value.toLowerCase()
}

/** Validates and converts a sandbox file-handle marker. */
function pushFileHandle(object: Record<string, unknown>): ValueNode {
  if (typeof object.path !== 'string') throw new TypeError('MontyFileHandle path must be a string')
  if (typeof object.mode !== 'string') throw new TypeError('MontyFileHandle mode must be a string')
  const position = object.position === undefined ? 0 : object.position
  validateFilePosition(position)
  return {
    tag: 'file-handle',
    val: { path: object.path, mode: canonicalFileMode(object.mode), position: BigInt(position) },
  }
}

/** Appends key/value pairs while preserving their insertion order. */
function pushPairs(pairs: [unknown, unknown][], nodes: ValueNode[]): NodePair[] {
  return pairs.map(([key, value]) => ({ key: pushValue(key, nodes), value: pushValue(value, nodes) }))
}

/** Fetches a raw arena node by index with the same bounds/cycle checks as
 *  `readValue`, for callers that must inspect the node's tag. The index stays
 *  marked as visiting, so a parent cycle in class-type nodes throws instead
 *  of recursing forever. */
function readValueNode(index: number, nodes: ValueNode[], visiting: Set<number>): ValueNode {

View on GitHub (pinned to adc986b362)

Solutions

  1. Pass the mode as a string: `mode: 'r'`, `mode: 'w'`, `mode: 'rb'`, etc.
  2. If you have a numeric mode from another API, map it to its string form before constructing the marker.
  3. Validate before the call: `typeof handle.mode === 'string'`.
  4. Check for undefined — a missing `mode` property produces this same error, not a default.

Example fix

// before
val.mode = 0  // numeric FileMode
// after
val.mode = 'r'
Defensive patterns

Strategy: type-guard

Validate before calling

const FILE_MODES = new Set(['r', 'w', 'a', 'x', 'r+', 'w+', 'a+', 'x+', 'rb', 'wb', 'ab', 'xb', 'rb+', 'wb+', 'ab+', 'xb+']);
if (typeof handle.mode !== 'string' || !FILE_MODES.has(handle.mode)) {
  throw new TypeError(`expected string file mode, got ${handle.mode}`);
}

Type guard

const hasStringMode = (h: unknown): h is { mode: string; [k: string]: unknown } =>
  typeof h === 'object' && h !== null && typeof (h as any).mode === 'string';

Try / catch

try {
  await session.feedRun(code, { inputs: { fh: handle } });
} catch (e) {
  if (e instanceof TypeError && e.message === 'MontyFileHandle mode must be a string') {
    handle.mode = NUMERIC_MODE_TO_STRING[handle.mode] ?? 'r';
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a file-handle marker with `mode: 0`, `mode: null`, or an omitted `mode` property; numeric FileMode enum values from another binding passed directly into the JS API instead of the string form.

Common situations: Mixing bindings (Python/Rust numeric file modes vs JS string modes); constructing markers by hand and forgetting mode; deserialization that turned the mode into null.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/e66e678cbc00986c. Report an issue: GitHub.