{"record":{"id":"66d972a6a04159e4","repo":"BabylonJS/Babylon.js","slug":"property-propertykey-tostring-of-object-66d972","errorCode":null,"errorMessage":"Property \"${propertyKey.toString()}\" of object \"${target}\" is not configurable.","messagePattern":"Property \"(.+?)\" of object \"(.+?)\" is not configurable\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts","lineNumber":85,"sourceCode":"    const ownerAndDescriptor = GetPropertyDescriptor(target, propertyKey);\r\n\r\n    // If the property does not exist, we'll define one transiently directly on the target object.\r\n    const [propertyOwner, propertyDescriptor] = ownerAndDescriptor ?? [\r\n        target,\r\n        {\r\n            configurable: true,\r\n            enumerable: true,\r\n            writable: true,\r\n            value: undefined,\r\n        },\r\n    ];\r\n\r\n    if (!ownerAndDescriptor) {\r\n        Reflect.defineProperty(propertyOwner, propertyKey, propertyDescriptor);\r\n    } else {\r\n        // If the property is not configurable, it cannot be intercepted.\r\n        if (!propertyDescriptor.configurable) {\r\n            throw new Error(`Property \"${propertyKey.toString()}\" of object \"${target}\" is not configurable.`);\r\n        }\r\n\r\n        // If the property is not writable, it cannot be intercepted, but it cannot be mutated anyway so there is no need to intercept it.\r\n        if (IsPropertyReadonly(propertyDescriptor)) {\r\n            return {\r\n                dispose: () => {},\r\n            };\r\n        }\r\n    }\r\n\r\n    // Get or create the hooks map for the target object.\r\n    let hooksMap = InterceptorHooksMaps.get(target);\r\n    if (!hooksMap) {\r\n        InterceptorHooksMaps.set(target, (hooksMap = new Map()));\r\n    }\r\n\r\n    // Get or create the hooks array for the property key.\r\n    let hooksForKey = hooksMap.get(propertyKey);\r","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts#L67-L103","documentation":"InterceptProperty redefines a property with a getter/setter wrapper to run hooks; this is only possible for configurable properties. When the property already exists (directly or inherited) and its descriptor reports configurable:false, the library refuses to intercept it and throws instead of silently failing to install hooks.","triggerScenarios":"Calling InterceptProperty (directly or via watchProperty) on a property declared with configurable:false — e.g. class methods/fields using decorators that seal them, frozen objects, built-in objects like Math/JSON properties, or properties created via Object.defineProperty without configurable:true.","commonSituations":"Watching properties on frozen state objects, on built-in host objects (DOM elements, Math, window.location), on TypeScript 'readonly' fields emitted with writable:false plus configurable:false, or third-party objects hardened by libraries like @ungap or Immer.","solutions":["Verify with Object.getOwnPropertyDescriptor(owner, key).configurable before watching; skip non-configurable keys.","Avoid Object.freeze/seal on objects whose properties you want to watch.","If you control the definition, define the property with configurable:true.","Watch an owning wrapper object or use a Proxy around the target instead of intercepting the property directly.","If the property is readonly, note InterceptProperty silently returns a no-op disposable; only configurable but writable:false paths throw here."],"exampleFix":"// before\nconst state = Object.freeze({ value: 1 });\nInterceptProperty(state, \"value\", { afterSet: log }); // throws\n\n// after\nconst state = { value: 1 }; // keep configurable\nconst d = InterceptProperty(state, \"value\", { afterSet: log });\nd.dispose();","handlingStrategy":"validation","validationCode":"function isInterceptable(target: object, key: PropertyKey): boolean {\n  let owner: object | null = target;\n  while (owner) {\n    const d = Reflect.getOwnPropertyDescriptor(owner, key);\n    if (d) return d.configurable === true;\n    owner = Reflect.getPrototypeOf(owner);\n  }\n  return true; // nonexistent props are created transiently\n}\nif (!isInterceptable(obj, \"value\")) throw new Error(\"skip interception\");","typeGuard":"function isConfigurableProperty(target: object, key: PropertyKey): boolean {\n  const d = Reflect.getOwnPropertyDescriptor(target, key);\n  return d ? d.configurable : true;\n}","tryCatchPattern":"let disposable: IDisposable;\ntry {\n  disposable = InterceptProperty(obj, key, hooks);\n} catch (e) {\n  if (e instanceof Error && /is not configurable/.test(e.message)) {\n    disposable = { dispose: () => {} }; // gracefully skip\n  } else throw e;\n}","preventionTips":["Check descriptor.configurable before watching any property.","Do not freeze/seal objects you intend to instrument.","Prefer intercepting plain state objects over built-ins (Math, DOM, window.location).","If you define properties yourself, always set configurable:true on instrumentable ones.","Treat TypeScript readonly/const-asserted object fields as likely non-configurable at runtime after hardening."],"tags":["instrumentation","reflection","property-descriptor","non-configurable"],"backgroundTag":"property-not-configurable","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}