{"record":{"id":"aad2b69018dfc5cb","repo":"JuliusBrussee/caveman","slug":"assemble-requires-model-and-sessionid","errorCode":null,"errorMessage":"assemble requires model and sessionId","messagePattern":"assemble requires model and sessionId","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1816,"sourceCode":"\n  return {\n    items: selectedIDs.map((id) => byID.get(id)!),\n    tokensUsed,\n    tokensBefore,\n    tokensSaved,\n    deferredCount,\n    deferredIds: [...deferredIDs],\n    basis: \"inferred\"\n  };\n}\n\nconst 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 {","sourceCodeStart":1798,"sourceCodeEnd":1834,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1798-L1834","documentation":"cave.assemble() validates its options before building a cache-optimal request from the given slots. Both `model` and `sessionId` are required because the assembly hash ledger is keyed per session and the emitted cache hints are model-specific. The SDK throws this Error locally when either field is empty/undefined, before any hashing or network work happens.","triggerScenarios":"Calling `cave.assemble({ provider, slots, ... })` (or `Cave.assemble`) with `options.model` undefined/empty string, `options.sessionId` undefined/empty string, or both — e.g. an options object built dynamically where model comes from an unset config value.","commonSituations":"Assembling requests in a loop where the model name is read from an env var or config that was never set; refactoring call sites and forgetting sessionId when it was previously inferred elsewhere; constructing AssembleOptions from user input that omits fields; TypeScript `as` casts or JS callers bypassing the type checker.","solutions":["Pass a non-empty `model` string (e.g. \"claude-sonnet-4\") and a non-empty `sessionId` in the AssembleOptions object.","If the model is dynamic, validate the config/env value before calling assemble and substitute a default.","Generate a sessionId explicitly (crypto.randomUUID()) if your workflow has no natural session id.","In TypeScript, let the AssembleOptions type catch the omission at compile time instead of casting partial objects with `as`."],"exampleFix":"// before\ncave.assemble({ provider: \"anthropic\", slots });\n\n// after\ncave.assemble({\n  provider: \"anthropic\",\n  model: \"claude-sonnet-4\",\n  sessionId: process.env.SESSION_ID ?? crypto.randomUUID(),\n  slots\n});","handlingStrategy":"validation","validationCode":"function validateAssembleOptions(opts) {\n  if (!opts.model) throw new Error(\"assemble: model is required\");\n  if (!opts.sessionId) throw new Error(\"assemble: sessionId is required\");\n}","typeGuard":"function canAssemble(opts) {\n  return typeof opts.model === \"string\" && opts.model.length > 0\n    && typeof opts.sessionId === \"string\" && opts.sessionId.length > 0;\n}","tryCatchPattern":"try {\n  const result = cave.assemble(opts);\n} catch (e) {\n  if (e.message.includes(\"requires model and sessionId\")) {\n    // fix options: supply model/sessionId before retrying\n  }\n  throw e;\n}","preventionTips":["Always construct AssembleOptions through a single factory that asserts model and sessionId.","Default sessionId to crypto.randomUUID() in the factory.","Avoid `as` casts on partial option objects; let the type checker flag omissions."],"tags":["validation","typescript","assembly","missing-argument"],"backgroundTag":"missing-required-argument","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"}