{"record":{"id":"aa2c21e72db5c065","repo":"paperclipai/paperclip","slug":"invalid-run-event-payload-encoding","errorCode":null,"errorMessage":"Invalid run-event payload encoding","messagePattern":"Invalid run-event payload encoding","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/db/src/run-event-payload.ts","lineNumber":42,"sourceCode":"      return Object.fromEntries(Object.entries(value).map(([key, entry]) => [\n        projectString(key), project(entry),\n      ]));\n    }\n    return value;\n  }\n\n  const projection = project(original) as Record<string, unknown>;\n  if (!needsEncoding) return originalJson;\n  // JSON.stringify escapes the original JSON a second time: PostgreSQL receives\n  // literal backslashes, not an unsupported U+0000. Decode before hashing/replay.\n  return JSON.stringify({ ...projection, [originalJsonKey]: originalJson });\n}\n\nexport function decodeRunEventPayload(value: string | Record<string, unknown>): Record<string, unknown> {\n  const payload = typeof value === \"string\" ? JSON.parse(value) as Record<string, unknown> : value;\n  if (Object.hasOwn(payload, originalJsonKey)) {\n    const originalJson = payload[originalJsonKey];\n    if (typeof originalJson !== \"string\") throw new Error(\"Invalid run-event payload encoding\");\n    const original: unknown = JSON.parse(originalJson);\n    if (original === null || typeof original !== \"object\" || Array.isArray(original)) {\n      throw new Error(\"Invalid run-event payload encoding\");\n    }\n    return original as Record<string, unknown>;\n  }\n  return payload;\n}\n\n// The SQL type stays JSONB; existing rows and SQL routing queries are unchanged.\nexport const runEventPayload = customType<{\n  data: Record<string, unknown>;\n  driverData: string;\n}>({\n  dataType: () => \"jsonb\",\n  toDriver: encodeRunEventPayload,\n  fromDriver: decodeRunEventPayload,\n});","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/db/src/run-event-payload.ts#L24-L60","documentation":"decodeRunEventPayload decodes the JSONB run-event payload stored by encodeRunEventPayload. When a stored payload contains the reserved on-disk marker key \"$paperclipRunEventJsonV1\", its value must be a string holding the double-encoded original JSON; if the marker's value is any other type (number, object, null, boolean), the encoding contract is violated and this error is thrown instead of returning corrupted data.","triggerScenarios":"Calling decodeRunEventPayload (directly or via the runEventPayload custom Drizzle type's fromDriver) on a payload object or JSON string where payload[\"$paperclipRunEventJsonV1\"] exists but is not a string — e.g. hand-written DB rows, migrated data, or code that stored an object under the reserved key.","commonSituations":"Manual SQL inserts into heartbeat_run_events that copied the marker key without the encoded string; a custom ETL/migration rewriting payloads; application code accidentally writing a field named $paperclipRunEventJsonV1 into its payload, triggering the encode path and confusing the decoder; version drift between writer and reader of the payload format.","solutions":["Never use the reserved key \"$paperclipRunEventJsonV1\" in your own payload fields; rename the application field","Fix or delete the malformed row so the marker value is the JSON-string produced by encodeRunEventPayload (or drop the marker entirely for plain payloads)","Re-encode the payload with encodeRunEventPayload and write it back before decoding","If migrating data, ensure the writer that produces the marker runs the same package version as the reader"],"exampleFix":"// before (malformed stored payload)\n{ \"$paperclipRunEventJsonV1\": { \"nested\": true } }\n// after (valid: marker holds the original JSON as a string)\n{ \"$paperclipRunEventJsonV1\": \"{\\\"nested\\\":true}\" }\n// or simply remove the reserved key from application payloads","handlingStrategy":"type-guard","validationCode":"const marker = \"$paperclipRunEventJsonV1\";\nfunction isDecodablePayload(p: unknown): boolean {\n  if (typeof p !== \"object\" || p === null || Array.isArray(p)) return false;\n  if (!Object.hasOwn(p, marker)) return true;\n  const v = (p as Record<string, unknown>)[marker];\n  if (typeof v !== \"string\") return false;\n  const orig: unknown = JSON.parse(v);\n  return orig !== null && typeof orig === \"object\" && !Array.isArray(orig);\n}","typeGuard":"function isValidEncodedMarker(v: unknown): v is string {\n  if (typeof v !== \"string\") return false;\n  try {\n    const orig: unknown = JSON.parse(v);\n    return orig !== null && typeof orig === \"object\" && !Array.isArray(orig);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const payload = decodeRunEventPayload(raw);\n  handle(payload);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Invalid run-event payload encoding\") {\n    // treat row as corrupt: quarantine payloadId and log raw value\n    await quarantineRunEvent(raw);\n  } else throw err;\n}","preventionTips":["Never use the reserved key \"$paperclipRunEventJsonV1\" as an application payload field","Only pass Record<string, unknown> payloads through encodeRunEventPayload; wrap anything else in an object","Write rows only via the runEventPayload Drizzle custom type, not raw SQL with hand-built JSON","Keep writer and reader on the same package version when the payload format evolves"],"tags":["database","json","encoding","payload"],"backgroundTag":"invalid-argument-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}