BabylonJS/Babylon.js · error
Failed to restore original property descriptor "${propertyKe
Error message
Failed to restore original property descriptor "${propertyKey.toString()}" on object "${target}". What it means
When the last hook for a property is disposed, InterceptProperty restores the original property descriptor captured at interception time using Reflect.defineProperty. This error means the restore was rejected, leaving the getter/setter wrapper (and hooks map entry already removed) in place, so the property stays permanently intercepted.
Source
Thrown at packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts:164
if (hooksForKey.length === 0) {
hooksMap.delete(propertyKey);
// If there are no more hooks for the target object, remove the hooks map from the WeakMap.
if (hooksMap.size === 0) {
InterceptorHooksMaps.delete(target);
}
const shouldRestorePropertyDescriptor =
// If the property is owned by the target object, then we may have replaced an original property descriptor that needs to be restore.
propertyOwner === target &&
// But this is only the case if we found an existing property descriptor on the target object (hence the ownerAndDescriptor check),
// or if the property value is not undefined, in which case we still want to retain the value that was set.
(ownerAndDescriptor || target[propertyKey] !== undefined);
// Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from the target object.
if (shouldRestorePropertyDescriptor) {
if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {
throw new Error(`Failed to restore original property descriptor "${propertyKey.toString()}" on object "${target}".`);
}
} else {
if (!Reflect.deleteProperty(target, propertyKey)) {
throw new Error(`Failed to delete transient property descriptor "${propertyKey.toString()}" on object "${target}".`);
}
}
}
isDisposed = true;
}
},
};
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Avoid freezing/sealing the target while a property interceptor is active; dispose first, then freeze.
- Check that no other code redefined the property during the interception window.
- Perform dispose during teardown while the object is still extensible and the property configurable.
- If the throw occurs, manually restore with Object.defineProperty(target, key, originalDescriptor) captured before interception, in a try/catch.
- If restore is impossible, recreate the object from its state snapshot.
Example fix
// before
const d = InterceptProperty(obj, "value", { afterSet: log });
Object.freeze(obj);
d.dispose(); // throws
// after
const d = InterceptProperty(obj, "value", { afterSet: log });
d.dispose();
Object.freeze(obj); Defensive patterns
Strategy: try-catch
Validate before calling
// before dispose
const d = Reflect.getOwnPropertyDescriptor(target, key);
if (!d || !d.configurable) console.warn("cannot restore descriptor; dispose later or rebuild");
if (Object.isFrozen(target)) console.warn("target frozen; deferred restore"); Type guard
function isRestorable(target: object, key: PropertyKey): boolean {
if (Object.isFrozen(target)) return false;
const d = Reflect.getOwnPropertyDescriptor(target, key);
return !d || d.configurable;
} Try / catch
try {
disposable.dispose();
} catch (e) {
if (e instanceof Error && /Failed to restore original property descriptor/.test(e.message)) {
try { Reflect.defineProperty(target, key, savedDescriptor); } catch { /* recreate object */ }
} else throw e;
} Prevention
- Keep a copy of the original descriptor so you can restore manually if dispose throws.
- Dispose watchers before freezing/hardening the object.
- Avoid redefining watched properties with other libraries during interception.
- Run disposals in teardown code that executes before cleanup/hardening phases.
- Fail loudly in dev: log restore failures so shadowing wrappers are noticed.
When it happens
Trigger: Disposing an InterceptProperty disposable when the target has been frozen/sealed since interception, the property was redefined as non-configurable by other code while hooked, or a new own data property replaced the accessor in a way that blocks redefining with the original descriptor.
Common situations: Test teardown after Object.freeze on state objects; disposing watchers on hot-reloaded modules whose descriptors changed; interactions with other monkey-patching libraries that redefine the same property; strict-mode hardened objects (e.g. frozen globals).
Related errors
- Failed to delete transient function "${propertyKey.toString(
- Property "${propertyKey.toString()}" of object "${target}" i
- Failed to define new property "${propertyKey.toString()}" on
- Failed to delete transient property descriptor "${propertyKe
- Property "${propertyKey.toString()}" of object "${target}" i
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/ece7e0bded18a2b6.
Report an issue: GitHub.