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

  1. Strip or transform non-serializable fields before storing (JSON.parse(JSON.stringify()) on a safe subset)
  2. Replace BigInt with string, functions with ids, and break circular refs explicitly
  3. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/03baedff8c34dba5. Report an issue: GitHub.