{"record":{"id":"d61cb7d1c6594273","repo":"BabylonJS/Babylon.js","slug":"failed-to-define-new-property-propertykey-tostr","errorCode":null,"errorMessage":"Failed to define new property \"${propertyKey.toString()}\" on object \"${target}\".","messagePattern":"Failed to define new property \"(.+?)\" on object \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts","lineNumber":133,"sourceCode":"\r\n        if (\r\n            // Replace the property with a new one that calls the hooks in addition to the original getter and setter.\r\n            !Reflect.defineProperty(target, propertyKey, {\r\n                configurable: true,\r\n                get: getValue\r\n                    ? function (this: unknown) {\r\n                          return getValue!.call(this);\r\n                      }\r\n                    : undefined,\r\n                set: function (this: unknown, newValue: unknown) {\r\n                    setValue.call(this, newValue);\r\n                    for (const { afterSet } of hooksForKey!) {\r\n                        afterSet?.(newValue);\r\n                    }\r\n                },\r\n            })\r\n        ) {\r\n            throw new Error(`Failed to define new property \"${propertyKey.toString()}\" on object \"${target}\".`);\r\n        }\r\n    }\r\n    hooksForKey.push(hooks as PropertyHooks<unknown>);\r\n\r\n    let isDisposed = false;\r\n    return {\r\n        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 as PropertyHooks<unknown>), 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","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/inspector-v2/src/instrumentation/propertyInstrumentation.ts#L115-L151","documentation":"After checking configurability, InterceptProperty installs the hook by redefining the property with a get/set pair via Reflect.defineProperty. This error means that call returned false — the redefinition was rejected at the last step, typically because the object is non-extensible or the property became non-configurable concurrently, so no hooks could be installed.","triggerScenarios":"Reflect.defineProperty on the target returns false: usually Object.freeze/Object.preventExtensions on the target between descriptor lookup and definition, a conflicting property redefinition by other code, or (in rare engine cases) an invalid descriptor combination for a non-configurable existing property.","commonSituations":"Race conditions where another interceptor (or devtools/mock framework) rewrites the same property at the same time; freezing a store object after starting but before hook registration; intercepting properties on objects made non-extensible by class decorators or hardening utilities.","solutions":["Check Object.isFrozen(target) / Object.isExtensible(target) before calling InterceptProperty and skip frozen objects.","Make sure only one instrumentation system touches the same property at a time; dispose other interceptors first.","Register hooks during setup, before any freeze/seal of the target.","If you own the object, keep it extensible while instrumentation is active.","Catch the error and fall back to polling/dirty-checking the value instead of property interception."],"exampleFix":"// before\nconst store = createStore();\nObject.freeze(store); // too early\nconst d = InterceptProperty(store, \"count\", { afterSet: log }); // throws\n\n// after\nconst store = createStore();\nconst d = InterceptProperty(store, \"count\", { afterSet: log });\nd.dispose();\nObject.freeze(store);","handlingStrategy":"validation","validationCode":"function canRedefine(target: object, key: PropertyKey): boolean {\n  if (!Object.isExtensible(target)) return false;\n  const d = Reflect.getOwnPropertyDescriptor(target, key);\n  return !d || d.configurable;\n}\nif (!canRedefine(store, \"count\")) throw new Error(\"object not redefinable\");","typeGuard":"function isRedefinable(target: object, key: PropertyKey): target is object {\n  return Object.isExtensible(target) &&\n    (!(key in target) || Reflect.getOwnPropertyDescriptor(target, key)!.configurable);\n}","tryCatchPattern":"let disposable: IDisposable;\ntry {\n  disposable = InterceptProperty(obj, key, hooks);\n} catch (e) {\n  if (e instanceof Error && /Failed to define new property/.test(e.message)) {\n    disposable = { dispose: () => {} }; // fall back to polling the value\n  } else throw e;\n}","preventionTips":["Register all interceptors before any Object.freeze/preventExtensions call.","Avoid two instrumentation systems targeting the same property concurrently.","Check Object.isExtensible(target) immediately before intercepting.","Freeze objects only after all disposables have been disposed.","Consider a Proxy wrapper as an alternative that cannot fail at define time."],"tags":["instrumentation","reflection","property-descriptor","frozen-object"],"backgroundTag":"defineproperty-failed","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}