{"record":{"id":"47ceddc1fa8f75e6","repo":"BabylonJS/Babylon.js","slug":"failed-to-restore-original-function-propertykey","errorCode":null,"errorMessage":"Failed to restore original function \"${propertyKey.toString()}\" on object \"${target}\".","messagePattern":"Failed to restore original function \"(.+?)\" on object \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts","lineNumber":99,"sourceCode":"        dispose: () => {\r\n            if (!isDisposed) {\r\n                // Remove the hooks from the hooks array for the property key.\r\n                hooksForKey.splice(hooksForKey.indexOf(hooks), 1);\r\n\r\n                // If there are no more hooks for the property key, remove the property from the hooks map.\r\n                if (hooksForKey.length === 0) {\r\n                    hooksMap.delete(propertyKey);\r\n\r\n                    // If there are no more hooks for the target object, remove the hooks map from the WeakMap.\r\n                    if (hooksMap.size === 0) {\r\n                        InterceptorHooksMaps.delete(target);\r\n                    }\r\n\r\n                    if (propertyDescriptor) {\r\n                        // If we have a property descriptor, it means the property was defined directly on the target object,\r\n                        // in which case we replaced it and the original property descriptor needs to be restored.\r\n                        if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {\r\n                            throw new Error(`Failed to restore original function \"${propertyKey.toString()}\" on object \"${target}\".`);\r\n                        }\r\n                    } else {\r\n                        // Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from\r\n                        // the target object to allow it to fall back to the prototype chain as it did originally.\r\n                        if (!Reflect.deleteProperty(target, propertyKey)) {\r\n                            throw new Error(`Failed to delete transient function \"${propertyKey.toString()}\" on object \"${target}\".`);\r\n                        }\r\n                    }\r\n                }\r\n\r\n                isDisposed = true;\r\n            }\r\n        },\r\n    };\r\n}\r\n","sourceCodeStart":81,"sourceCodeEnd":115,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts#L81-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst h = InterceptFunction(player, 'play', hooks);\nObject.freeze(player); // hardening before dispose\nh.dispose(); // throws: cannot restore\n// after\nconst h = InterceptFunction(player, 'play', hooks);\nh.dispose(); // restore first\nObject.freeze(player); // then harden","handlingStrategy":"try-catch","validationCode":"function canRestore(target: object): boolean {\n  // The target must not have been frozen/sealed after interception\n  return !Object.isFrozen(target) && !Object.isSealed(target);\n}\n// call before disposing:\nif (!canRestore(target)) throw new Error('target was frozen; restore would fail');","typeGuard":"function isRestorable(target: object): boolean {\n  return !Object.isFrozen(target) && !Object.isSealed(target);\n}","tryCatchPattern":"try {\n  handle.dispose();\n} catch (e) {\n  if (String((e as Error)?.message).includes('Failed to restore original function')) {\n    console.error(`Could not restore ${String(propertyKey)}; object may have been frozen during interception`);\n    // best-effort manual restore or flag object as still-instrumented\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["instrumentation","dispose","restore","frozen-object","function-interception"],"backgroundTag":"restore-original-function-failed","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}