{"record":{"id":"61be2aae6d857827","repo":"pydantic/monty","slug":"wrapper-id-id-already-identifies-a-different-object-in-this","errorCode":null,"errorMessage":"wrapper id '${id}' already identifies a different object in this session","messagePattern":"wrapper id '(.+?)' already identifies a different object in this session","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":459,"sourceCode":"   *  is not yet registered — used for the `ClassType` a `ClassInstance`\n   *  materializes, so an auto-built default policy never clobbers an\n   *  explicitly granted one. Still rejects an id aliasing a different object. */\n  registerClassIfAbsent(wrapper: ClassType): string {\n    this.checkNoAlias(wrapper.id, wrapper.classType)\n    if (!this.map.has(wrapper.id)) {\n      this.map.set(wrapper.id, wrapper)\n    }\n    return wrapper.id\n  }\n\n  /** Throws if `id` is already registered for an object other than `value`\n   *  (compared by identity). Two wrappers sharing an id but wrapping\n   *  different objects would silently re-route method calls and round-trips\n   *  from one host object to the other. */\n  private checkNoAlias(id: string, value: object): void {\n    const existing = this.map.get(id)\n    if (existing !== undefined && existing.instance !== value) {\n      throw new TypeError(`wrapper id '${id}' already identifies a different object in this session`)\n    }\n  }\n\n  /** Looks up the wrapper registered for `id` (instance or class type). */\n  get(id: string): BaseWrapper | undefined {\n    return this.map.get(id)\n  }\n}\n\n/**\n * Internal sentinel thrown by [`ClassInstance.lookupLazyAttr`] /\n * [`ClassInstance.callMethod`] when a name is outside the wrapper's policy or\n * absent; the session layer turns it into a sandbox `AttributeError`. Not\n * exported from the package index.\n */\nexport class AttrNotExposed extends Error {\n  constructor(message: string) {\n    super(message)","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L441-L477","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Use a distinct uuid for each distinct object; generate ids with uuid4 or omit `id` so one is generated.","Re-send the same wrapper (or another wrapper of the same object) — re-registering the same object under its id is allowed and overwrites.","Check `store.get(id)` and only register when it is undefined or wraps the same object (`wrapper.instance === value`).","If restoring dumps, make sure the id pinned via options.id matches the object it was originally issued for."],"exampleFix":"// before\nstore.register(new ClassInstance(userA, { id: myId }))\nstore.register(new ClassInstance(userB, { id: myId })) // TypeError: alias\n// after\nstore.register(new ClassInstance(userA, { id: idForA }))\nstore.register(new ClassInstance(userB, { id: generateUuid() }))","handlingStrategy":"validation","validationCode":"function safeRegister(store: InstanceStore, wrapper: ClassInstance): string {\n  const existing = store.get(wrapper.id)\n  if (existing !== undefined && existing.instance !== wrapper.instance) {\n    throw new TypeError(`id ${wrapper.id} already used for a different object`)\n  }\n  return store.register(wrapper)\n}","typeGuard":"function idIsFreeOrSame(store: InstanceStore, id: string, value: object): boolean {\n  const existing = store.get(id)\n  return existing === undefined || existing.instance === value\n}","tryCatchPattern":"try {\n  store.register(wrapper)\n} catch (e) {\n  if (e instanceof TypeError && /already identifies a different object/.test(e.message)) {\n    // regenerate identity for the second object rather than aliasing\n    wrapper = new ClassInstance(wrapper.instance) // fresh uuid4 id\n    store.register(wrapper)\n  } else throw e\n}","preventionTips":["Only pin ids (options.id) when restoring dumps; let wrappers auto-generate uuids otherwise","Never reuse a pinned id constant for a second object in the same session","Keep a host-side map object→id and always look it up before choosing an id","If ids come from external input (snapshot files), validate they match the object identity before registering"],"tags":["typescript","identity","session-state","conflict"],"backgroundTag":"invalid-identifier","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}