BabylonJS/Babylon.js · error

Failed to delete transient function "${propertyKey.toString(

Error message

Failed to delete transient function "${propertyKey.toString()}" on object "${target}".

What it means

InterceptFunction temporarily replaces an inherited function by defining an own property on the target object. On dispose, when no descriptor existed on the target (the function was inherited), it tries Reflect.deleteProperty to restore the prototype-chain lookup. This error means that delete returned false, so the transient own property could not be removed and the object is left with the hooked wrapper still installed.

Source

Thrown at packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts:105

                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

  1. Check whether the target was frozen/sealed or the property became non-configurable between InterceptFunction and dispose; avoid freezing objects under instrumentation.
  2. Dispose interceptors before applying Object.freeze/seal to the target object.
  3. Ensure no other library redefines the intercepted property while the hook is active.
  4. Wrap dispose in try/catch and, if it throws, manually restore the object (delete the own property or re-create the target).
  5. As a last resort, discard/rebuild the target object since its cleanup failed.

Example fix

// before
Object.freeze(camera);
const d = InterceptFunction(camera, "render", { afterCall: log });
d.dispose(); // throws on dispose

// after
const d = InterceptFunction(camera, "render", { afterCall: log });
d.dispose();
Object.freeze(camera);
Defensive patterns

Strategy: try-catch

Validate before calling

// before disposing
if (Object.isFrozen(target) || !Object.isExtensible(target)) {
  console.warn("Target frozen; deferred disposal of function interceptor");
}
const own = Reflect.getOwnPropertyDescriptor(target, key);
if (own && !own.configurable) console.warn("Transient function prop became non-configurable");

Type guard

function isDisposableTarget(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 delete transient function/.test(e.message)) {
    try { delete (target as any)[key]; } catch { /* recreate target */ }
  } else throw e;
}

Prevention

When it happens

Trigger: Calling InterceptFunction on a function inherited from the prototype chain, then disposing the returned IDisposable when the target has since been frozen (Object.freeze) or sealed, or the own property was redefined as non-configurable by other code while the hook was active.

Common situations: Intercepting engine/built-in prototype methods during hot-reload or inspector tooling, or disposing interceptors in tests after other instrumentation (e.g. a second interceptor or a mock framework) rewrote the property with configurable:false. Also happens with Object.freeze(target) applied mid-session.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/b1b8c5e58bcaf0c4. Report an issue: GitHub.