paperclipai/paperclip · warning

dropping 1 event with a non-serializable dimension…

Error message

dropping 1 event with a non-serializable dimension (circular reference?); event="${chunk[0]?.name}"

What it means

While size-splitting a batch, the serialized byte count for a single-event envelope is not a finite number — classically because JSON.stringify failed or degenerated on a circular reference inside an event dimension. The event is dropped with this warning and never reaches the collector.

Solutions

  1. Find the emit site for the named event and replace the raw object with a flat, explicitly-selected DTO.
  2. If the object must go through, run it through a JSON-safe replacer that drops circular keys before emit.
  3. Add a dev-mode assertion that JSON.stringify(event) succeeds for every emit.
  4. Prefer primitive fields (ids, names, counts) over nested objects in dimensions.

Example fix

// before
telemetry.emit({ name: 'task_inspected', dims: { task } });

// after
telemetry.emit({ name: 'task_inspected', dims: { taskId: task.id, status: task.status } });
Defensive patterns

Strategy: validation

Validate before calling

function isJsonSafe(value: unknown, seen = new WeakSet()): boolean {
  if (value === null || typeof value !== 'object') return true;
  if (seen.has(value as object)) return false;
  seen.add(value as object);
  return Object.values(value as object).every((v) => isJsonSafe(v, seen));
}

// before emit:
if (!isJsonSafe(event)) throw new Error(`Circular reference in telemetry event ${event.name}`);

Type guard

function isSerializableTelemetryEvent(e: TelemetryEvent): boolean {
  try {
    return Number.isFinite(Buffer.byteLength(JSON.stringify(e)));
  } catch {
    return false;
  }
}

Prevention

When it happens

Trigger: A telemetry dimension contains an object graph with a cycle: parent↔child associations, self-referential error.cause chains, ORM rows with relations, AST/scope objects captured wholesale.

Common situations: Passing live domain objects instead of DTOs; logging `error.cause` chains that loop; serializing request/response objects with back-references; reusing parsed structures that reference their container.

Related errors


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/5b48294583649479. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/src/telemetry/client.ts:190

   */
  private chunkForSend(events: TelemetryEvent[]): TelemetryEvent[][] {
    const maxCount = Math.max(1, this.caps.maxEventsPerBatch);
    const out: TelemetryEvent[][] = [];
    for (let i = 0; i < events.length; i += maxCount) {
      this.splitByBytes(events.slice(i, i + maxCount), out);
    }
    return out;
  }

  private splitByBytes(chunk: TelemetryEvent[], out: TelemetryEvent[][]): void {
    if (chunk.length === 0) return;
    const bytes = this.serializedBytes(this.buildEnvelope(chunk));
    if (bytes <= this.caps.maxBodyBytes) {
      out.push(chunk);
      return;
    }
    if (chunk.length === 1) {
      this.warn(
        Number.isFinite(bytes)
          ? `dropping 1 event whose serialized envelope exceeds maxBodyBytes (${this.caps.maxBodyBytes} bytes); event="${chunk[0]?.name}"`
          : `dropping 1 event with a non-serializable dimension (circular reference?); event="${chunk[0]?.name}"`,
      );
      return;
    }
    const mid = Math.ceil(chunk.length / 2);
    this.splitByBytes(chunk.slice(0, mid), out);
    this.splitByBytes(chunk.slice(mid), out);
  }

  private buildEnvelope(events: TelemetryEvent[], batchId?: string): TelemetryEventEnvelope {
    const state = this.getState();
    return {
      app: this.config.app ?? "paperclip",
      schemaVersion: this.config.schemaVersion ?? "1",
      installId: state.installId,
      version: this.version,

View on GitHub (pinned to 120ae5428f)