{"record":{"id":"55bf0fa7fc02bed5","repo":"BabylonJS/Babylon.js","slug":"failed-to-delete-transient-property-descriptor","errorCode":null,"errorMessage":"Failed to delete transient property descriptor \"${propertyKey.toString()}\" on object \"${target}\".","messagePattern":"Failed to delete transient property descriptor \"(.+?)\" on object \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts","lineNumber":168,"sourceCode":"                    if (hooksMap.size === 0) {\r\n                        InterceptorHooksMaps.delete(target);\r\n                    }\r\n\r\n                    const shouldRestorePropertyDescriptor =\r\n                        // If the property is owned by the target object, then we may have replaced an original property descriptor that needs to be restore.\r\n                        propertyOwner === target &&\r\n                        // But this is only the case if we found an existing property descriptor on the target object (hence the ownerAndDescriptor check),\r\n                        // or if the property value is not undefined, in which case we still want to retain the value that was set.\r\n                        (ownerAndDescriptor || target[propertyKey] !== undefined);\r\n                    // Otherwise, the property was inherited through the prototype chain, and so we can simply delete it from the target object.\r\n\r\n                    if (shouldRestorePropertyDescriptor) {\r\n                        if (!Reflect.defineProperty(target, propertyKey, propertyDescriptor)) {\r\n                            throw new Error(`Failed to restore original property descriptor \"${propertyKey.toString()}\" on object \"${target}\".`);\r\n                        }\r\n                    } else {\r\n                        if (!Reflect.deleteProperty(target, propertyKey)) {\r\n                            throw new Error(`Failed to delete transient property descriptor \"${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":150,"sourceCodeEnd":178,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts#L150-L178","documentation":"On final dispose, if the intercepted property did not exist on the target originally (it was inherited or newly transient), the library deletes the transient accessor with Reflect.deleteProperty so lookups fall back to the prototype chain. This error means the delete returned false, so the transient property remains on the object and shadows the prototype member.","triggerScenarios":"Disposing InterceptProperty where the property was transient, but the target became frozen/sealed (non-configurable own property cannot be deleted), or the transient property was redefined with configurable:false while hooked.","commonSituations":"Freezing a scene/store object while property watchers are active; teardown ordering where a hardening step runs before disposals; another interceptor converting the transient accessor to a non-configurable one.","solutions":["Dispose all InterceptProperty disposables before freezing/sealing the target.","Verify Object.isFrozen(target) before disposal; unfreeze or rebuild if frozen.","Ensure no other code redefines the transient property to configurable:false while hooked.","Capture the state and rebuild the object if deleteProperty keeps failing.","As a workaround, set the transient property to undefined instead of relying on deletion, then recreate the object cleanly."],"exampleFix":"// before\nconst d = InterceptProperty(instance, \"inheritedProp\", { afterSet: log });\nObject.freeze(instance);\nd.dispose(); // throws\n\n// after\nconst d = InterceptProperty(instance, \"inheritedProp\", { afterSet: log });\nd.dispose();\nObject.freeze(instance);","handlingStrategy":"try-catch","validationCode":"// before dispose\nconst d = Reflect.getOwnPropertyDescriptor(target, key);\nif (d && !d.configurable) console.warn(\"transient property cannot be deleted; defer or rebuild\");\nif (Object.isFrozen(target)) console.warn(\"frozen target; deleteProperty will fail\");","typeGuard":"function canDeleteProperty(target: object, key: PropertyKey): boolean {\n  if (Object.isFrozen(target)) return false;\n  const d = Reflect.getOwnPropertyDescriptor(target, key);\n  return !d || d.configurable;\n}","tryCatchPattern":"try {\n  disposable.dispose();\n} catch (e) {\n  if (e instanceof Error && /Failed to delete transient property descriptor/.test(e.message)) {\n    try { delete (target as any)[key]; } catch { /* recreate object */ }\n  } else throw e;\n}","preventionTips":["Freeze/seal objects only after all property watchers are disposed.","Track whether a property was transient (created by interception) and verify it is still configurable at teardown.","Avoid interleaving other interceptors that might make the transient property non-configurable.","If deletion fails repeatedly, rebuild the object from a state snapshot.","Log dispose errors to detect objects left shadowing prototype members."],"tags":["instrumentation","reflection","dispose","deleteproperty"],"backgroundTag":"reflect-deleteproperty-failed","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}