{"record":{"id":"17f031f7eeccdecb","repo":"JuliusBrussee/caveman","slug":"assembly-slot-json-stringify-slot-id-content-i","errorCode":null,"errorMessage":"assembly slot ${JSON.stringify(slot.id)} content is not JSON-serializable","messagePattern":"assembly slot (.+?) content is not JSON-serializable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1837,"sourceCode":"  }\n\n  const ids = new Set<string>();\n  let ledger = assemblyHashLedgers.get(cave);\n  if (!ledger) {\n    ledger = new Map<string, string>();\n    assemblyHashLedgers.set(cave, ledger);\n  }\n  const normalized = options.slots.map((slot) => {\n    if (!slot.id || ids.has(slot.id)) throw new Error(`assembly slot id must be non-empty and unique: ${JSON.stringify(slot.id)}`);\n    ids.add(slot.id);\n    if (![\"stable\", \"session\", \"volatile\"].includes(slot.stability)) {\n      throw new Error(`assembly slot ${JSON.stringify(slot.id)} has unknown stability ${JSON.stringify(slot.stability)}`);\n    }\n    let contentJSON: string;\n    try {\n      contentJSON = canonicalJSON(slot.content);\n    } catch {\n      throw new Error(`assembly slot ${JSON.stringify(slot.id)} content is not JSON-serializable`);\n    }\n    const contentHash = sha256Hex(contentJSON);\n    if (slot.stability !== \"volatile\") {\n      const key = `${options.sessionId}\\0${slot.id}`;\n      const previous = ledger.get(key);\n      if (previous !== undefined && previous !== contentHash) throw new AssemblyStabilityError(slot.id);\n      ledger.set(key, contentHash);\n    }\n    return { ...slot, content: JSON.parse(contentJSON) as unknown };\n  });\n\n  const ordered = ([\"stable\", \"session\", \"volatile\"] as const)\n    .flatMap((stability) => normalized.filter((slot) => slot.stability === stability));\n  const prefixSlots = ordered.filter((slot) => slot.stability !== \"volatile\");\n  const volatileSlots = ordered.filter((slot) => slot.stability === \"volatile\");\n\n  let request: Record<string, unknown>;\n  let prefixBytes: string;","sourceCodeStart":1819,"sourceCodeEnd":1855,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1819-L1855","documentation":"assemble() serializes each slot's `content` through canonicalJSON() to compute the content hash used for cache-prefix tracking. If serialization throws (circular structures, BigInt values, functions/symbols, or a custom canonicalizer rejecting non-JSON types), the SDK rethrows as this plain Error naming the offending slot.","triggerScenarios":"Passing slot content containing non-JSON-serializable values: circular object references, BigInt, functions, class instances with methods that the canonicalizer walks, `undefined` in arrays, or very deep structures blowing the serializer stack.","commonSituations":"Putting SDK/provider client objects or tool handles directly into slot content instead of plain data; content pulled from structured-clone contexts (DB drivers, WASM boundaries) carrying BigInt; caching parsed objects with parent pointers creating cycles; passing a function as content by mistake.","solutions":["Ensure every slot's `content` is plain JSON data — strings, numbers, booleans, null, arrays, and plain objects.","JSON.stringify a test of the content (or run your own cycle check) before assembling to find the offending value.","Strip or convert non-JSON values: `String(bigintValue)` for BigInt, omit functions, break cycles by building a plain snapshot.","Wrap the content in a typed serialization boundary (e.g. a toWire() method) so only plain data ever reaches slots."],"exampleFix":"// before\nslots.push({ id: \"tools\", stability: \"session\", content: { registry: toolRegistry } }); // circular\n\n// after\nslots.push({\n  id: \"tools\",\n  stability: \"session\",\n  content: { registry: toolRegistry.list().map((t) => ({ name: t.name, schema: t.schema })) }\n});","handlingStrategy":"validation","validationCode":"function isJsonSafe(value, seen = new WeakSet()) {\n  if (value === null || typeof value !== \"object\") {\n    return typeof value !== \"function\" && typeof value !== \"symbol\" && typeof value !== \"bigint\";\n  }\n  if (seen.has(value)) return false;\n  seen.add(value);\n  return Object.values(value).every((v) => isJsonSafe(v, seen));\n}","typeGuard":"function hasSerializableContent(slot) {\n  try { JSON.stringify(slot.content); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  return cave.assemble(opts);\n} catch (e) {\n  if (e.message.includes(\"content is not JSON-serializable\")) {\n    const slotId = e.message.match(/assembly slot (\".*\")/)?.[1];\n    // replace the offending slot's content with a plain-data snapshot and retry once\n  }\n  throw e;\n}","preventionTips":["Keep slot content as plain JSON data; never embed client objects, functions, or class instances.","Convert BigInt to string/number at the boundary; strip cyclic parent pointers.","Add a JSON.stringify smoke test over slot content in unit tests.","Define a toWire()/snapshot() method per content producer so only plain data reaches slots."],"tags":["validation","json","serialization","assembly"],"backgroundTag":"json-serialization-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}