BabylonJS/Babylon.js · error
Failed to restore original function "${propertyKey.toString(
Error message
Failed to restore original function "${propertyKey.toString()}" on object "${target}". What it means
When an interception is disposed, InterceptFunction restores the original property descriptor it saved at instrumentation time via Reflect.defineProperty. If that restore call returns false, the original function could not be put back and this error is thrown, leaving the object in an instrumented state. This signals the object became frozen/sealed or the descriptor became incompatible after interception began.
Source
Thrown at packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts:99
dispose: () => {
if (!isDisposed) {
// Remove the hooks from the hooks array for the property key.
hooksForKey.splice(hooksForKey.indexOf(hooks), 1);
// If there are no more hooks for the property key, remove the property from the hooks map.
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);
}
if (propertyDescriptor) {
// If we have a property descriptor, it means the property was defined directly on the target object,
// in which case we replaced it and the original property descriptor needs to be restored.
if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {
throw new Error(`Failed to restore original function "${propertyKey.toString()}" on object "${target}".`);
}
} else {
// Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from
// the target object to allow it to fall back to the prototype chain as it did originally.
if (!Reflect.deleteProperty(target, propertyKey)) {
throw new Error(`Failed to delete transient function "${propertyKey.toString()}" on object "${target}".`);
}
}
}
isDisposed = true;
}
},
};
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Dispose interceptors before any code freezes or seals the target object; fix cleanup ordering (dispose in teardown before hardening).
- Verify the property was not redefined elsewhere while intercepted; remove competing redefine calls.
- Catch this on dispose and manually restore: Object.defineProperty(target, key, savedDescriptor), then verify Reflect.get returns the original.
- Avoid intercepting shared/global objects that other code may freeze; intercept a private wrapper object instead.
Example fix
// before const h = InterceptFunction(player, 'play', hooks); Object.freeze(player); // hardening before dispose h.dispose(); // throws: cannot restore // after const h = InterceptFunction(player, 'play', hooks); h.dispose(); // restore first Object.freeze(player); // then harden
Defensive patterns
Strategy: try-catch
Validate before calling
function canRestore(target: object): boolean {
// The target must not have been frozen/sealed after interception
return !Object.isFrozen(target) && !Object.isSealed(target);
}
// call before disposing:
if (!canRestore(target)) throw new Error('target was frozen; restore would fail'); Type guard
function isRestorable(target: object): boolean {
return !Object.isFrozen(target) && !Object.isSealed(target);
} Try / catch
try {
handle.dispose();
} catch (e) {
if (String((e as Error)?.message).includes('Failed to restore original function')) {
console.error(`Could not restore ${String(propertyKey)}; object may have been frozen during interception`);
// best-effort manual restore or flag object as still-instrumented
return;
}
throw e;
} Prevention
- Dispose interceptors before any code freezes/seals the target.
- Keep interceptor lifetime short; dispose in teardown/finalizers.
- Do not share instrumented objects with code that hardens or redefines properties.
- Save the original descriptor yourself and be prepared to restore manually if dispose fails.
When it happens
Trigger: Calling dispose() on the handle returned by InterceptFunction when Reflect.defineProperty(target, propertyKey, originalDescriptor) fails — typically because the target was frozen/sealed (Object.freeze/seal/preventExtensions) after interception, or another script replaced the property with an incompatible non-configurable one.
Common situations: Tooling that freezes global/window objects while an inspector hook is active; cleanup ordering where a hardening step runs before the interceptor is disposed; long-lived interceptors on shared objects mutated by other libraries; restore racing with code that redefined the property.
Related errors
- Property "${propertyKey.toString()}" of object "${target}" i
- Property "${propertyKey.toString()}" of object "${target}" i
- Property "${propertyKey.toString()}" of object "${target}" i
- Failed to define new function "${propertyKey.toString()}" on
- Failed to delete transient function "${propertyKey.toString(
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/47ceddc1fa8f75e6.
Report an issue: GitHub.