pydantic/monty · error · TypeError

wrapper id '${id}' already identifies a different object in

Error message

wrapper id '${id}' already identifies a different object in this session

What it means

InstanceStore keys wrappers by session-local uuid; ids must be unambiguous or method calls and round-trips would silently route to the wrong host object. checkNoAlias (used by register, registerClass and registerClassIfAbsent) throws this TypeError when the id is already mapped to a different object — compared by strict identity of the wrapped instance/class.

Source

Thrown at crates/monty-js/ts/classInstance.ts:459

   *  is not yet registered — used for the `ClassType` a `ClassInstance`
   *  materializes, so an auto-built default policy never clobbers an
   *  explicitly granted one. Still rejects an id aliasing a different object. */
  registerClassIfAbsent(wrapper: ClassType): string {
    this.checkNoAlias(wrapper.id, wrapper.classType)
    if (!this.map.has(wrapper.id)) {
      this.map.set(wrapper.id, wrapper)
    }
    return wrapper.id
  }

  /** Throws if `id` is already registered for an object other than `value`
   *  (compared by identity). Two wrappers sharing an id but wrapping
   *  different objects would silently re-route method calls and round-trips
   *  from one host object to the other. */
  private checkNoAlias(id: string, value: object): void {
    const existing = this.map.get(id)
    if (existing !== undefined && existing.instance !== value) {
      throw new TypeError(`wrapper id '${id}' already identifies a different object in this session`)
    }
  }

  /** Looks up the wrapper registered for `id` (instance or class type). */
  get(id: string): BaseWrapper | undefined {
    return this.map.get(id)
  }
}

/**
 * Internal sentinel thrown by [`ClassInstance.lookupLazyAttr`] /
 * [`ClassInstance.callMethod`] when a name is outside the wrapper's policy or
 * absent; the session layer turns it into a sandbox `AttributeError`. Not
 * exported from the package index.
 */
export class AttrNotExposed extends Error {
  constructor(message: string) {
    super(message)

View on GitHub (pinned to adc986b362)

Solutions

  1. Use a distinct uuid for each distinct object; generate ids with uuid4 or omit `id` so one is generated.
  2. Re-send the same wrapper (or another wrapper of the same object) — re-registering the same object under its id is allowed and overwrites.
  3. Check `store.get(id)` and only register when it is undefined or wraps the same object (`wrapper.instance === value`).
  4. If restoring dumps, make sure the id pinned via options.id matches the object it was originally issued for.

Example fix

// before
store.register(new ClassInstance(userA, { id: myId }))
store.register(new ClassInstance(userB, { id: myId })) // TypeError: alias
// after
store.register(new ClassInstance(userA, { id: idForA }))
store.register(new ClassInstance(userB, { id: generateUuid() }))
Defensive patterns

Strategy: validation

Validate before calling

function safeRegister(store: InstanceStore, wrapper: ClassInstance): string {
  const existing = store.get(wrapper.id)
  if (existing !== undefined && existing.instance !== wrapper.instance) {
    throw new TypeError(`id ${wrapper.id} already used for a different object`)
  }
  return store.register(wrapper)
}

Type guard

function idIsFreeOrSame(store: InstanceStore, id: string, value: object): boolean {
  const existing = store.get(id)
  return existing === undefined || existing.instance === value
}

Try / catch

try {
  store.register(wrapper)
} catch (e) {
  if (e instanceof TypeError && /already identifies a different object/.test(e.message)) {
    // regenerate identity for the second object rather than aliasing
    wrapper = new ClassInstance(wrapper.instance) // fresh uuid4 id
    store.register(wrapper)
  } else throw e
}

Prevention

When it happens

Trigger: Explicitly passing the same `id` option to wrappers of two different objects (`new ClassInstance(a, { id })` then `new ClassInstance(b, { id })`) or to a ClassType whose class differs from what the id already maps to, then sending the second wrapper in the same session (prepare/registerClass).

Common situations: Hard-coding or copying a pinned id from a snapshot-restoration setup into a second wrapper; restoring a dump in a fresh session where a different object was already registered under that id; generating ids from unstable input (e.g. name hashing) that collide.

Related errors


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