paperclipai/paperclip · error
Chat SDK ${kind} state is not JSON-serializable
Error message
Chat SDK ${kind} state is not JSON-serializable What it means
Before persisting, PaperclipChatSdkStateAdapter serializes the state envelope with JSON.stringify; if that throws (circular references, BigInt, functions, non-serializable objects), the adapter rethrows as this error with the original cause attached. State must round-trip through JSON by contract.
Source
Thrown at server/src/services/chat-sdk-state.ts:501
}
private expiryFromTtl(ttlMs: number | undefined): Date | null {
if (ttlMs === undefined || ttlMs === 0) return null;
assertTtl("State TTL", ttlMs);
return new Date(this.now().getTime() + ttlMs);
}
private envelope(kind: StateKind, value: unknown): StateEnvelope {
const envelope: StateEnvelope = {
kind,
schemaVersion: STATE_SCHEMA_VERSION,
value,
};
let serialized: string;
try {
serialized = JSON.stringify(envelope);
} catch (error) {
throw new Error(`Chat SDK ${kind} state is not JSON-serializable`, {
cause: error,
});
}
if (Buffer.byteLength(serialized) > this.maxValueBytes) {
throw new Error(
`Chat SDK ${kind} state exceeds the ${this.maxValueBytes}-byte limit`,
);
}
return envelope;
}
private async compareAndSet(
key: string,
expectedVersion: number | null,
kind: StateKind,
value: unknown,
expiresAt: Date | null,
): Promise<boolean> {View on GitHub (pinned to 01ad858492)
Solutions
- Strip or transform non-serializable fields before storing (JSON.parse(JSON.stringify()) on a safe subset)
- Replace BigInt with string, functions with ids, and break circular refs explicitly
- Inspect error.cause for the original JSON.stringify failure point
Example fix
// before
await state.set(kind, key, sdkResponse); // contains circular refs
// after
await state.set(kind, key, JSON.parse(JSON.stringify({ id: sdkResponse.id, status: sdkResponse.status }))); Defensive patterns
Strategy: validation
Validate before calling
function isJsonSafe(v: unknown): boolean { try { JSON.stringify(v); return true; } catch { return false; } } Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> { return typeof v === 'object' && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype; } Try / catch
try { await state.set(kind, key, value); } catch (err) { if (/not JSON-serializable/.test((err as Error).message)) { console.error('cause:', (err as Error & { cause?: unknown }).cause); value = sanitize(value); return state.set(kind, key, value); } throw err; } Prevention
- Never store raw SDK/class objects; store plain DTOs
- Replace BigInt with strings before persisting
- Check error.cause to find the offending field
When it happens
Trigger: Calling set/state APIs with an object containing circular references, BigInt values, functions, class instances with toJSON throwers, or other JSON-incompatible values as the state payload.
Common situations: Storing raw agent SDK response objects that hold circular parent links; BigInt ids from databases; passing Error objects or callbacks in state; version upgrade where payloads gained non-serializable fields.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- Expected a JSON object: ${path}
- Public report campaign metadata is invalid
- Expected a JSON object: ${path}
- dropping 1 event with a non-serializable dimension (circular
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/03baedff8c34dba5.
Report an issue: GitHub.