{"record":{"id":"e78121bd4e925f2e","repo":"JuliusBrussee/caveman","slug":"assembly-slot-id-must-be-non-empty-and-unique-j","errorCode":null,"errorMessage":"assembly slot id must be non-empty and unique: ${JSON.stringify(slot.id)}","messagePattern":"assembly slot id must be non-empty and unique: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1828,"sourceCode":"const assemblyHashLedgers = new WeakMap<Cave, Map<string, string>>();\nconst assemblyRequestHeaders = new WeakMap<object, string>();\n\nfunction assemble(cave: Cave, options: AssembleOptions): AssemblyResult {\n  const provider = options.provider.trim().toLowerCase();\n  const emitCacheHints = options.emitCacheHints ?? \"gateway\";\n  if (!options.model || !options.sessionId) throw new Error(\"assemble requires model and sessionId\");\n  if (![\"gateway\", \"self\", \"none\"].includes(emitCacheHints)) {\n    throw new Error(`unknown emitCacheHints value ${JSON.stringify(emitCacheHints)}`);\n  }\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 };","sourceCodeStart":1810,"sourceCodeEnd":1846,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1810-L1846","documentation":"Every slot passed to assemble() must have a non-empty `id` that is unique across the slots array; slot ids feed the content-hash ledger and the emitted breakpoint names. assemble() collects ids into a Set and throws this Error as soon as a slot has an empty id or repeats an earlier id.","triggerScenarios":"Passing an AssemblySlot with `id: \"\"` or `id: undefined` (JS callers), or two slots with the same id — e.g. building slots in a loop with a constant id, or merging slot lists from two sources without deduplicating.","commonSituations":"Programmatic slot generation using a placeholder id that never got interpolated; concatenating per-feature slot arrays that both contain a \"system\" slot; an upstream refactor renaming slot fields so ids arrive empty; hand-written slot literals copy-pasted without changing the id.","solutions":["Give every slot a distinct, non-empty string id (e.g. \"system\", \"history-0\", \"tools\").","Deduplicate before assembling: filter duplicate ids or suffix them (\"system-2\").","If ids are generated, assert they are filled in before building the options object.","Validate the slots array with a pre-check loop and fail early with a clear message identifying the offending entry."],"exampleFix":"// before\nconst slots = [{ id: \"system\", stability: \"stable\", content }, ...extra.map(() => ({ id: \"system\", ... }))];\n\n// after\nconst slots = [\n  { id: \"system\", stability: \"stable\", content },\n  ...extra.map((s, i) => ({ id: `extra-${i}`, ...s }))\n];\nconst seen = new Set();\nfor (const s of slots) {\n  if (!s.id || seen.has(s.id)) throw new Error(`bad slot id: ${JSON.stringify(s.id)}`);\n  seen.add(s.id);\n}","handlingStrategy":"validation","validationCode":"function validateSlotIds(slots) {\n  const seen = new Set();\n  for (const s of slots) {\n    if (typeof s.id !== \"string\" || s.id.length === 0) throw new Error(`slot id missing: ${JSON.stringify(s)}`);\n    if (seen.has(s.id)) throw new Error(`duplicate slot id: ${s.id}`);\n    seen.add(s.id);\n  }\n}","typeGuard":"function hasUniqueIds(slots) {\n  return slots.every((s) => typeof s.id === \"string\" && s.id.length > 0)\n    && new Set(slots.map((s) => s.id)).size === slots.length;\n}","tryCatchPattern":"try {\n  return cave.assemble({ ...opts, slots });\n} catch (e) {\n  if (e.message.includes(\"slot id must be non-empty and unique\")) {\n    // log which slot literal caused it and correct the generator\n  }\n  throw e;\n}","preventionTips":["Generate slot ids from indexed loops (`feature-${i}`) rather than constants.","Deduplicate merged slot arrays before assembling.","Keep a naming convention for slot ids and review slot literals in code review."],"tags":["validation","assembly","duplicate-key","slots"],"backgroundTag":"invalid-argument-value","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"}