pydantic/monty · error · TypeError
id must be a canonical uuid string, got
Error message
${wrapperKind} id must be a canonical uuid string, got ${JSON.stringify(id)} What it means
Caller-supplied wrapper ids (ClassType or ClassInstance registration) must match the canonical uuid pattern; normalizeId rejects anything else and lowercases the accepted id so the id the sandbox reports back (always lowercase) matches the store key. This TypeError names the wrapper kind and shows the offending value as JSON.
Solutions
- Omit the id and let the library generate a canonical uuid
- Pass a canonical uuid (crypto.randomUUID() output is valid)
- Trim whitespace and strip braces/quotes, or normalize with crypto.randomUUID() before passing
Example fix
// before
const wrapper = ClassInstance(obj, type, { id: 'obj-123' });
// after
const wrapper = ClassInstance(obj, type, { id: crypto.randomUUID() }); Defensive patterns
Strategy: validation
Validate before calling
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (id !== undefined && !(typeof id === 'string' && UUID_RE.test(id))) {
throw new Error(`id must be a canonical uuid, got ${JSON.stringify(id)}`);
} Type guard
function isCanonicalUuid(v) {
return typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
} Try / catch
try {
const wrapper = ClassInstance(obj, type, { id });
} catch (e) {
if (e instanceof TypeError && e.message.includes('canonical uuid string')) {
throw new Error('supply crypto.randomUUID() or omit the id to auto-generate');
}
throw e;
} Prevention
- Generate ids with crypto.randomUUID() or omit the id entirely
- Never use short-id libraries (nanoid, cuid) for wrapper ids
- Trim and strip formatting from ids sourced from external systems before passing them
When it happens
Trigger: Calling ClassType or ClassInstance with an id that is a non-uuid string, has uppercase-and-odd formatting beyond hex-uuid, contains extra characters, or is a generated short id (e.g. nanoid/cuid) instead of a canonical uuid.
Common situations: Generating ids with non-uuid libraries, copying ids with surrounding whitespace or braces, using a database key that is not a uuid.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- ClassInstance marker instanceId must be a uuid string
- must be a canonical uuid string
- assertMessageAnnotations must be a boolean or an integer…
- at least one OpenTelemetry component is required
- can't have text and binary mode at once
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/0d8bad5addd4e877.
Report an issue: GitHub.
Appendix: source
Thrown at crates/monty-js/ts/classInstance.ts:746
/** Generates a canonical lowercase uuid4 string. `getRandomValues` rather than
* `randomUUID` so insecure browser contexts work too. */
function generateUuid(): string {
const bytes = crypto.getRandomValues(new Uint8Array(16))
bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80 // RFC 4122 variant
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}
/** Canonical 8-4-4-4-12 hex uuid; case-insensitive since `normalizeId` lowercases. */
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
/** Validates a caller-supplied wrapper id and lowercases it, so the id the
* sandbox reports back (always lowercase) is the key the store holds. */
function normalizeId(wrapperKind: string, id: string): string {
if (typeof id !== 'string' || !UUID_PATTERN.test(id)) {
throw new TypeError(`${wrapperKind} id must be a canonical uuid string, got ${JSON.stringify(id)}`)
}
return id.toLowerCase()
}
/** Rejects a string policy other than `'all'`: a bare `'greet'` would
* otherwise be treated as a character array (`'g'`, `'r'`, ...). */
function validatePolicy(field: string, policy: AttrPolicy | undefined): void {
if (typeof policy === 'string' && policy !== 'all') {
throw new TypeError(`${field} must be 'all', undefined or a list/Set of names, got '${policy}'`)
}
}
/** Names no policy may expose, `'all'` or explicit: JS object machinery
* that would hand the sandbox the class, its prototype, or a call stack. */
const DENIED_NAMES: ReadonlySet<string> = new Set(['constructor', '__proto__', 'prototype', 'arguments', 'caller'])
/** Whether `policy` exposes `name`; `'all'` never exposes underscore names,
* and [`DENIED_NAMES`] are refused whichever form the policy takes. */View on GitHub (pinned to adc986b362)