{"record":{"id":"2835ee65fb29a897","repo":"toeverything/AFFiNE","slug":"reactiveproxyerror-2835ee","errorCode":"ReactiveProxyError","errorMessage":"key cannot be a symbol","messagePattern":"key cannot be a symbol","errorType":"exception","errorClass":"BlockSuiteError","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/store/src/reactive/proxy.ts","lineNumber":52,"sourceCode":"\n          this._updateWithSkip(() => {\n            this._source.splice(retain, 0, ...proxyList);\n          });\n\n          retain += change.insert.length;\n        }\n      });\n    });\n  };\n\n  protected _getProxy = () => {\n    return new Proxy(this._source, {\n      has: (target, p) => {\n        return Reflect.has(target, p);\n      },\n      set: (target, p, value, receiver) => {\n        if (typeof p !== 'string') {\n          throw new BlockSuiteError(\n            ErrorCode.ReactiveProxyError,\n            'key cannot be a symbol'\n          );\n        }\n\n        const index = Number(p);\n        if (this._skipNext || Number.isNaN(index)) {\n          return Reflect.set(target, p, value, receiver);\n        }\n\n        if (this._stashed.has(index)) {\n          const result = Reflect.set(target, p, value, receiver);\n          this._options.onChange?.(this._proxy, true);\n          return result;\n        }\n\n        const reactive = proxies.get(this._ySource);\n        if (!reactive) {","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/blocksuite/framework/store/src/reactive/proxy.ts#L34-L70","documentation":"Thrown by the ReactiveYArray (and ReactiveYMap) Proxy `set`/`deleteProperty` traps when the property key `p` is not a string. The reactive layer translates every mutation into a Yjs transaction keyed by a string index/name; symbol keys have no Yjs representation, so they are rejected to prevent silent data loss (a symbol-keyed write would update the local proxy array but never sync).","triggerScenarios":"Assigning a symbol-keyed property on a reactive Yjs array/map proxy: arr[Symbol.iterator] = ..., or libraries that set symbol properties (e.g. some decorators, React internals, immer) on the proxied object; spreading/assigning objects with symbol keys onto a reactive prop.","commonSituations":"Third-party utilities (React devtools, profiling, instrumentation) tagging the proxied object with a symbol; user code doing Object.assign(reactiveProp, someObjWithSymbols); iterating with for-in vs for-of confusion; test doubles attaching metadata.","solutions":["Do not assign symbol-keyed properties to reactive Yjs proxies; keep symbol metadata on a separate plain object.","Before Object.assign/spread onto a reactive prop, strip symbol keys: filter Object.getOwnPropertySymbols out.","If a library needs to tag the object, wrap the reactive proxy in a plain carrier object and tag the carrier.","Audit third-party middleware that touches model.props for symbol writes."],"exampleFix":"// before: assigning an object that carries symbol keys\nObject.assign(block.props.style, maybeTaggedObj);\n\n// after: strip symbols before writing into the reactive proxy\nconst clean = Object.fromEntries(\n  Object.entries(maybeTaggedObj)\n);\nObject.assign(block.props.style, clean);","handlingStrategy":"type-guard","validationCode":"export function stripSymbols<T extends object>(obj: T): T {\n  for (const sym of Object.getOwnPropertySymbols(obj)) {\n    delete (obj as Record<symbol, unknown>)[sym];\n  }\n  return obj;\n}\n\n// before assigning onto a reactive proxy\nconst clean = Object.fromEntries(Object.entries(maybeTagged));\nObject.assign(block.props.style, clean);","typeGuard":"export const hasOnlyStringKeys = (obj: object): boolean =>\n  Object.getOwnPropertySymbols(obj).length === 0;","tryCatchPattern":"try {\n  Object.assign(reactiveProxy, value);\n} catch (e) {\n  if (e instanceof BlockSuiteError && e.code === ErrorCode.ReactiveProxyError && /symbol/.test(e.message)) {\n    const clean = Object.fromEntries(Object.entries(value));\n    Object.assign(reactiveProxy, clean);\n  } else throw e;\n}","preventionTips":["Never assign symbol-keyed properties onto reactive Yjs proxies.","Filter Object.getOwnPropertySymbols before Object.assign/spread onto reactive props.","Keep symbol metadata on a separate plain carrier object, not the proxy.","Audit third-party middleware (React devtools, instrumentation) for symbol writes."],"tags":["reactive-proxy","proxy","symbol","yjs"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}