{"record":{"id":"4a0fc2cc8c4fe299","repo":"paperclipai/paperclip","slug":"photon-state-record-is-too-large","errorCode":null,"errorMessage":"Photon state record is too large","messagePattern":"Photon state record is too large","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/photon/state.ts","lineNumber":25,"sourceCode":"/** Typed provider records share the existing company/endpoint scoped CAS store. */\nexport class PhotonState {\n  constructor(\n    readonly scope: ChatSdkStateScope,\n    private readonly persistence: ChatSdkStatePersistence,\n  ) {}\n  private key(key: string): string {\n    return `photon:${createHash(\"sha256\").update(key).digest(\"hex\")}`;\n  }\n  async read<T>(key: string): Promise<T | null> {\n    const row = await this.persistence.read(this.scope, this.key(key));\n    return row ? (row.value as T) : null;\n  }\n  async update<T>(key: string, update: (current: T | null) => T): Promise<T> {\n    for (let attempt = 0; attempt < 32; attempt++) {\n      const row = await this.persistence.read(this.scope, this.key(key));\n      const value = update(row ? (row.value as T) : null);\n      if (Buffer.byteLength(JSON.stringify(value)) > 512 * 1024)\n        throw new Error(\"Photon state record is too large\");\n      if (\n        await this.persistence.compareAndSet({\n          ...this.scope,\n          key: this.key(key),\n          expectedVersion: row?.version ?? null,\n          expiresAt: null,\n          value,\n        })\n      )\n        return value;\n    }\n    throw new Error(\"Photon state changed concurrently; retry\");\n  }\n}\n","sourceCodeStart":7,"sourceCodeEnd":40,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/state.ts#L7-L40","documentation":"PhotonState.update() performs a compare-and-set update of a JSON-serialized record in the shared chat-sdk state store. Before writing, it checks the serialized value size and throws this plain Error if it exceeds the hard cap of 512 KB. The store's rows must stay small (they share the company/endpoint scoped CAS table), so oversized state documents are rejected rather than persisted.","triggerScenarios":"Calling update() with an updater function that returns a value whose JSON.stringify exceeds 512 * 1024 bytes — e.g. appending unbounded items to an array, accumulating logs/events in state, or storing large blobs (attachments, long transcripts) instead of references.","commonSituations":"An append-only list that grows across many runs until it crosses the cap; storing full agent transcripts or large tool outputs in state instead of an external store; a bug causing duplicate entries to be merged on every update.","solutions":["Move large payloads out of PhotonState into external storage and store only a reference/ID in the state record","Cap or prune collections inside the updater (e.g. keep only the last N entries) before returning the value","Split one large key into multiple smaller keys so each record stays under 512 KB","Add a size guard/log in the updater to detect which field is growing unboundedly"],"exampleFix":"// before: unbounded accumulation\nawait state.update(\"events\", (cur) => [...(cur ?? []), event]);\n// after: bounded window, overflow to external store\nawait state.update(\"events\", (cur) => {\n  const next = [...(cur ?? []), eventRef];\n  return next.length > 500 ? next.slice(next.length - 500) : next;\n});","handlingStrategy":"validation","validationCode":"function assertStateSize<T>(value: T, label: string): T {\n  const bytes = Buffer.byteLength(JSON.stringify(value));\n  if (bytes > 480 * 1024) // margin under the 512 KB hard cap\n    throw new Error(`${label} state would be ${bytes} bytes; prune or externalize before update()`);\n  return value;\n}\n// usage: await state.update(\"events\", (cur) => assertStateSize(next(cur), \"events\"));","typeGuard":"function isStateTooLargeError(e: unknown): boolean {\n  return e instanceof Error && e.message === \"Photon state record is too large\";\n}","tryCatchPattern":"try {\n  await state.update(key, updater);\n} catch (e) {\n  if (isStateTooLargeError(e)) {\n    await externalizeOverflow(key); // move big entries to object storage, keep refs\n    await state.update(key, pruneToCap);\n  } else throw e;\n}","preventionTips":["Treat state records as small documents: store IDs/references, never raw blobs or full transcripts","Bound every append-only collection in state (keep last N, archive the rest externally)","Log serialized size of large updates in development to catch growth trends before hitting 512 KB","Split monolithic state keys into several finer-grained keys"],"tags":["state","size-limit","storage"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}