paperclipai/paperclip · error · Error
Photon state record is too large
Error message
Photon state record is too large
What it means
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.
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
Example fix
// before: unbounded accumulation
await state.update("events", (cur) => [...(cur ?? []), event]);
// after: bounded window, overflow to external store
await state.update("events", (cur) => {
const next = [...(cur ?? []), eventRef];
return next.length > 500 ? next.slice(next.length - 500) : next;
}); Defensive patterns
Strategy: validation
Validate before calling
function assertStateSize<T>(value: T, label: string): T {
const bytes = Buffer.byteLength(JSON.stringify(value));
if (bytes > 480 * 1024) // margin under the 512 KB hard cap
throw new Error(`${label} state would be ${bytes} bytes; prune or externalize before update()`);
return value;
}
// usage: await state.update("events", (cur) => assertStateSize(next(cur), "events")); Type guard
function isStateTooLargeError(e: unknown): boolean {
return e instanceof Error && e.message === "Photon state record is too large";
} Try / catch
try {
await state.update(key, updater);
} catch (e) {
if (isStateTooLargeError(e)) {
await externalizeOverflow(key); // move big entries to object storage, keep refs
await state.update(key, pruneToCap);
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
- paperclip_runner_chat_attachment_source_size_mismatch
- ACPX session is not at a safe suspension point
- ACPX snapshot exceeds its byte bound
- Artifact exceeds size limit.
- Attachment exceeds the configured size limit
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/4a0fc2cc8c4fe299.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/state.ts:25
/** Typed provider records share the existing company/endpoint scoped CAS store. */
export class PhotonState {
constructor(
readonly scope: ChatSdkStateScope,
private readonly persistence: ChatSdkStatePersistence,
) {}
private key(key: string): string {
return `photon:${createHash("sha256").update(key).digest("hex")}`;
}
async read<T>(key: string): Promise<T | null> {
const row = await this.persistence.read(this.scope, this.key(key));
return row ? (row.value as T) : null;
}
async update<T>(key: string, update: (current: T | null) => T): Promise<T> {
for (let attempt = 0; attempt < 32; attempt++) {
const row = await this.persistence.read(this.scope, this.key(key));
const value = update(row ? (row.value as T) : null);
if (Buffer.byteLength(JSON.stringify(value)) > 512 * 1024)
throw new Error("Photon state record is too large");
if (
await this.persistence.compareAndSet({
...this.scope,
key: this.key(key),
expectedVersion: row?.version ?? null,
expiresAt: null,
value,
})
)
return value;
}
throw new Error("Photon state changed concurrently; retry");
}
}
View on GitHub (pinned to 3f1d897a7c)