{"record":{"id":"dac6ddf7dad6b9f6","repo":"pydantic/monty","slug":"must-have-exactly-one-of-create-read-write-append-mode-and","errorCode":null,"errorMessage":"Must have exactly one of create/read/write/append mode and at most one plus","messagePattern":"Must have exactly one of create/read/write/append mode and at most one plus","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/types.ts","lineNumber":122,"sourceCode":"  }\n\n  /** Whether the mode permits writes. */\n  get writable(): boolean {\n    return this.mode.startsWith('w') || this.mode.startsWith('a') || this.mode.includes('+')\n  }\n\n  /** Treats handles decoded by the native transport as instances too. */\n  static [Symbol.hasInstance](value: unknown): boolean {\n    return (\n      typeof value === 'object' && value !== null && (value as Record<string, unknown>).__monty_type__ === 'FileHandle'\n    )\n  }\n}\n\n/** Canonicalizes the subset of Python file modes Monty supports. */\nexport function canonicalFileMode(mode: string): string {\n  if (mode.length === 0) {\n    throw new TypeError('Must have exactly one of create/read/write/append mode and at most one plus')\n  }\n\n  let action: string | undefined\n  let binary = false\n  let text = false\n  for (const char of mode) {\n    if (char === 'r' || char === 'w' || char === 'a') {\n      if (action !== undefined) throw new TypeError('must have exactly one of create/read/write/append mode')\n      action = char\n    } else if (char === 'x') {\n      throw new TypeError('exclusive creation mode is not supported')\n    } else if (char === 'b') {\n      if (binary) throw new TypeError('invalid mode: binary mode specified twice')\n      binary = true\n    } else if (char === 't') {\n      if (text) throw new TypeError('invalid mode: text mode specified twice')\n      text = true\n    } else if (char === '+') {","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/types.ts#L104-L140","documentation":"`canonicalFileMode` parses the Python-style open mode and requires exactly one primary action character (r/w/a) plus at most one '+'; an empty mode string fails immediately. This mirrors CPython's `open()` mode validation for the subset Monty supports.","triggerScenarios":"Calling `new MontyFileHandle(path, '')` or returning a file handle from an `open` OS callback with an empty-string mode.","commonSituations":"The host builds the mode dynamically (e.g. concatenating flags) and ends up with an empty string when no flag matched; or the sandbox requested a mode that the callback maps to nothing.","solutions":["Pass a non-empty mode string: 'r', 'w', 'a', optionally with '+' (e.g. 'r+', 'w+').","Check the mode-mapping logic that produced the empty string and default to 'r' when no action is requested.","Validate the mode before constructing the handle."],"exampleFix":"// before\nconst mode = flags.includePlus ? '+' : ''; // empty when no plus\n\n// after\nconst mode = flags.includePlus ? 'r+' : 'r';","handlingStrategy":"validation","validationCode":"if (typeof mode !== 'string' || mode.length === 0) throw new TypeError('mode required, e.g. r/w/a plus optional +');","typeGuard":null,"tryCatchPattern":"try {\n  return new MontyFileHandle(path, mode);\n} catch (e) {\n  if (e instanceof TypeError && e.message.startsWith('Must have exactly one of')) {\n    return new MontyFileHandle(path, 'r');\n  }\n  throw e;\n}","preventionTips":["Never build mode strings by concatenation that can yield ''","Default the base action to 'r' when the sandbox does not specify one","Enumerate supported modes ('r','w','a','r+','w+','a+','rb','wb','ab') explicitly"],"tags":["file-mode","validation","file-handle","python-compat"],"backgroundTag":"empty-required-field","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}