BabylonJS/Babylon.js · error
Property "${propertyKey.toString()}" of object "${target}" i
Error message
Property "${propertyKey.toString()}" of object "${target}" is readonly. What it means
To install an interception, the library must overwrite the property with a replacement function and later restore it, so the property must be writable (or have a setter). If the descriptor shows writable === false, or it is a non-writable, setter-less accessor (writable undefined and no set), the property is readonly and interception is refused. This is thrown only when the property exists directly on the target (a descriptor was found).
Source
Thrown at packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts:50
export function InterceptFunction<T extends object>(target: T, propertyKey: keyof T, hooks: FunctionHooks): IDisposable {
if (!hooks.afterCall) {
throw new Error("At least one hook must be provided.");
}
const originalFunction = Reflect.get(target, propertyKey, target) as (...args: any) => any;
if (typeof originalFunction !== "function") {
throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not a function.`);
}
// Make sure the property is configurable and writable, otherwise it is immutable and cannot be intercepted.
const propertyDescriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
if (propertyDescriptor) {
if (!propertyDescriptor.configurable) {
throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not configurable.`);
}
if (propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set)) {
throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is readonly.`);
}
}
// Get or create the hooks map for the target object.
let hooksMap = InterceptorHooksMaps.get(target);
if (!hooksMap) {
InterceptorHooksMaps.set(target, (hooksMap = new Map()));
}
// Get or create the hooks array for the property key.
let hooksForKey = hooksMap.get(propertyKey);
if (!hooksForKey) {
hooksMap.set(propertyKey, (hooksForKey = []));
if (
// Replace the function with a new one that calls the hooks in addition to the original function.
!Reflect.set(target, propertyKey, function (this: unknown, ...args: unknown[]) {
const result = Reflect.apply(originalFunction, this, args);
for (const { afterCall } of hooksForKey!) {
View on GitHub (pinned to 0592b347b8)
Solutions
- Define the property as writable (Object.defineProperty(obj, key, {value: fn, writable: true, configurable: true})) before intercepting.
- Add a setter to accessor-only properties, or restructure so the function is a writable data property.
- Avoid Object.freeze on objects you intend to instrument; freeze a copy instead and intercept the unfrozen original.
- Wrap the function at its definition site (pass the instrumented function in) instead of replacing the readonly property.
Example fix
// before
const cfg = Object.freeze({ onClick: fn });
InterceptFunction(cfg, 'onClick', hooks); // throws: readonly
// after
const cfg = { onClick: fn }; // do not freeze if interception is needed
InterceptFunction(cfg, 'onClick', hooks); Defensive patterns
Strategy: validation
Validate before calling
function isWritable(target, propertyKey) {
const d = Reflect.getOwnPropertyDescriptor(target, propertyKey);
if (!d) return true; // inherited or absent: handled elsewhere
return d.writable === true || (d.writable === undefined && typeof d.set === 'function');
}
if (!isWritable(target, 'play')) throw new TypeError('target.play is readonly'); Type guard
function hasWritableDescriptor(target: object, key: PropertyKey): boolean {
const d = Object.getOwnPropertyDescriptor(target, key);
return d === undefined || d.writable === true || (d.writable === undefined && typeof d.set === 'function');
} Try / catch
try {
InterceptFunction(target, propertyKey, hooks);
} catch (e) {
if (String((e as Error)?.message).includes('is readonly')) {
console.warn(`Cannot intercept ${String(propertyKey)}: property is readonly`);
return null;
}
throw e;
} Prevention
- Verify descriptor writable/set before intercepting own properties.
- Do not Object.freeze objects containing methods you intend to hook.
- Prefer writable data properties over getter-only accessors for hookable callbacks.
- If you must hook a readonly property, wrap it at the definition/call site instead.
When it happens
Trigger: Calling InterceptFunction on an own property defined with writable: false (e.g. via Object.defineProperty or Object.freeze), or an accessor with only a getter (no setter) so a new value cannot be assigned.
Common situations: Frozen state/config objects whose callback methods were frozen for safety; getter-only properties exposing a function (get handler() {...}); readonly module exports; constants frozen in library code you are trying to monkey-patch.
Related errors
- Property "${propertyKey.toString()}" of object "${target}" i
- Failed to define new property "${propertyKey.toString()}" on
- Failed to restore original function "${propertyKey.toString(
- Property "${propertyKey.toString()}" of object "${target}" i
- Failed to restore original property descriptor "${propertyKe
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/5b62b385ee2c9e7e.
Report an issue: GitHub.