{"record":{"id":"5b48294583649479","repo":"paperclipai/paperclip","slug":"dropping-1-event-with-a-non-serializable-dimension","errorCode":null,"errorMessage":"dropping 1 event with a non-serializable dimension (circular reference?); event=\"${chunk[0]?.name}\"","messagePattern":"dropping 1 event with a non-serializable dimension \\(circular reference\\?\\); event=\"(.+?)\"","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/shared/src/telemetry/client.ts","lineNumber":190,"sourceCode":"   */\n  private chunkForSend(events: TelemetryEvent[]): TelemetryEvent[][] {\n    const maxCount = Math.max(1, this.caps.maxEventsPerBatch);\n    const out: TelemetryEvent[][] = [];\n    for (let i = 0; i < events.length; i += maxCount) {\n      this.splitByBytes(events.slice(i, i + maxCount), out);\n    }\n    return out;\n  }\n\n  private splitByBytes(chunk: TelemetryEvent[], out: TelemetryEvent[][]): void {\n    if (chunk.length === 0) return;\n    const bytes = this.serializedBytes(this.buildEnvelope(chunk));\n    if (bytes <= this.caps.maxBodyBytes) {\n      out.push(chunk);\n      return;\n    }\n    if (chunk.length === 1) {\n      this.warn(\n        Number.isFinite(bytes)\n          ? `dropping 1 event whose serialized envelope exceeds maxBodyBytes (${this.caps.maxBodyBytes} bytes); event=\"${chunk[0]?.name}\"`\n          : `dropping 1 event with a non-serializable dimension (circular reference?); event=\"${chunk[0]?.name}\"`,\n      );\n      return;\n    }\n    const mid = Math.ceil(chunk.length / 2);\n    this.splitByBytes(chunk.slice(0, mid), out);\n    this.splitByBytes(chunk.slice(mid), out);\n  }\n\n  private buildEnvelope(events: TelemetryEvent[], batchId?: string): TelemetryEventEnvelope {\n    const state = this.getState();\n    return {\n      app: this.config.app ?? \"paperclip\",\n      schemaVersion: this.config.schemaVersion ?? \"1\",\n      installId: state.installId,\n      version: this.version,","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/packages/shared/src/telemetry/client.ts#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Find the emit site for the named event and replace the raw object with a flat, explicitly-selected DTO.","If the object must go through, run it through a JSON-safe replacer that drops circular keys before emit.","Add a dev-mode assertion that JSON.stringify(event) succeeds for every emit.","Prefer primitive fields (ids, names, counts) over nested objects in dimensions."],"exampleFix":"// before\ntelemetry.emit({ name: 'task_inspected', dims: { task } });\n\n// after\ntelemetry.emit({ name: 'task_inspected', dims: { taskId: task.id, status: task.status } });","handlingStrategy":"validation","validationCode":"function isJsonSafe(value: unknown, seen = new WeakSet()): boolean {\n  if (value === null || typeof value !== 'object') return true;\n  if (seen.has(value as object)) return false;\n  seen.add(value as object);\n  return Object.values(value as object).every((v) => isJsonSafe(v, seen));\n}\n\n// before emit:\nif (!isJsonSafe(event)) throw new Error(`Circular reference in telemetry event ${event.name}`);","typeGuard":"function isSerializableTelemetryEvent(e: TelemetryEvent): boolean {\n  try {\n    return Number.isFinite(Buffer.byteLength(JSON.stringify(e)));\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":null,"preventionTips":["Never pass live domain objects into dimensions; map to flat DTOs at the emit boundary.","Strip error.cause chains or replace with err.cause?.message.","Assert serializability in dev builds so cycles fail fast before production drops them.","Watch for this warn's telltale 'non-serializable dimension' phrasing during integration tests."],"tags":["telemetry","circular-reference","serialization","event-drop"],"backgroundTag":"circular-reference-json","analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}