pydantic/monty · error · TypeError

invalid mode: text mode specified twice

Error message

invalid mode: text mode specified twice

What it means

The mode string contained 't' more than once (e.g. 'rtt'). Text mode can only be specified once, and canonicalFileMode rejects duplicates with a TypeError, matching CPython's validation of mode strings.

Source

Thrown at crates/monty-js/ts/types.ts:138

export function canonicalFileMode(mode: string): string {
  if (mode.length === 0) {
    throw new TypeError('Must have exactly one of create/read/write/append mode and at most one plus')
  }

  let action: string | undefined
  let binary = false
  let text = false
  for (const char of mode) {
    if (char === 'r' || char === 'w' || char === 'a') {
      if (action !== undefined) throw new TypeError('must have exactly one of create/read/write/append mode')
      action = char
    } else if (char === 'x') {
      throw new TypeError('exclusive creation mode is not supported')
    } else if (char === 'b') {
      if (binary) throw new TypeError('invalid mode: binary mode specified twice')
      binary = true
    } else if (char === 't') {
      if (text) throw new TypeError('invalid mode: text mode specified twice')
      text = true
    } else if (char === '+') {
      throw new TypeError("update modes ('+') are not yet supported")
    } else {
      throw new TypeError(`invalid mode: '${char}'`)
    }
  }
  if (binary && text) throw new TypeError("can't have text and binary mode at once")
  if (action === undefined) {
    throw new TypeError('Must have exactly one of create/read/write/append mode and at most one plus')
  }
  return `${action}${binary ? 'b' : ''}`
}

/** Validates that a file position can cross the JavaScript boundary exactly. */
export function validateFilePosition(position: unknown): asserts position is number {
  if (typeof position !== 'number' || !Number.isSafeInteger(position) || position < 0) {
    throw new TypeError('MontyFileHandle position must be a non-negative safe integer')

View on GitHub (pinned to adc986b362)

Solutions

  1. Ensure 't' appears at most once in the mode string
  2. Drop 't' entirely — text is the default when 'b' is absent
  3. Normalize the mode before passing it: strip duplicate 't' characters

Example fix

// before
const mode = userMode + 't' // userMode was already 'rt'
// after
const mode = userMode.includes('t') || userMode.includes('b') ? userMode : userMode + 't'
Defensive patterns

Strategy: validation

Validate before calling

if (typeof mode === 'string' && (mode.match(/t/g) || []).length > 1) throw new Error('mode has duplicate t')

Type guard

function hasSingleT(mode) { return typeof mode === 'string' && (mode.match(/t/g) || []).length <= 1 }

Try / catch

try { fh = new MontyFileHandle(path, mode) } catch (e) { if (e instanceof TypeError && e.message.includes("text mode specified twice")) fh = new MontyFileHandle(path, mode.replace(/t(?=.*t)/, '')) ; else throw e }

Prevention

When it happens

Trigger: Passing a mode like 'rt', 'wt' where 't' appears twice ('rtt', 'wtt') to MontyFileHandle's constructor or pushFileHandle.

Common situations: Programmatic mode assembly that appends 't' as default text mode even when the user mode already included it.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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