{"record":{"id":"6c1cd721f8641962","repo":"JuliusBrussee/caveman","slug":"cave-checkpoint-response-missing-source-ref-or-mes","errorCode":null,"errorMessage":"cave checkpoint response missing source_ref or messages","messagePattern":"cave checkpoint response missing source_ref or messages","errorType":"exception","errorClass":"CaveRequestError","httpStatus":200,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":860,"sourceCode":"      return `[cave-artifact id=${response.artifact_id} source=${options.source} type=${options.contentType ?? \"application/json\"}]\\nsummary: artifact stored.\\nretrieve: authenticated GET /sdk/v1/artifacts/${response.artifact_id} or trace.artifacts.get(\"${response.artifact_id}\").\\n[/cave-artifact]`;\n    },\n    get: async (artifactId: string): Promise<unknown> => artifactGet(this.cave, artifactId, this.workflow, this.traceContext())\n  };\n\n  context = {\n    checkpoint: async (messages: unknown[], options: Record<string, unknown>) => request(this.cave, \"/sdk/v1/checkpoints\", { messages, options, workflow: this.workflow }, this.workflow, this.traceContext()),\n    /**\n     * Reverse a checkpoint `source_ref` back into the original context.\n     *\n     * GETs `/sdk/v1/checkpoints/{sourceRef}/expand`; the gateway returns the\n     * stored `{ source_ref, version, messages, checkpoint }`. This is the other\n     * half of `checkpoint()` — reversibility is mandatory: a checkpoint that\n     * cannot be expanded is a bug.\n     */\n    expand: async (sourceRef: string) => {\n      const response = await request(this.cave, `/sdk/v1/checkpoints/${encodeURIComponent(sourceRef)}/expand`, undefined, this.workflow, this.traceContext());\n      if (typeof response.source_ref !== \"string\" || response.source_ref.trim() === \"\" || !Array.isArray(response.messages)) {\n        throw new CaveRequestError(200, \"/sdk/v1/checkpoints/{sourceRef}/expand\", \"cave checkpoint response missing source_ref or messages\");\n      }\n      return response;\n    }\n  };\n}\n\n// ─── Runtime policy client ────────────────────────────────────────────────────\n\n/** Schema version this client accepts; anything else is rejected (fail closed). */\nconst RUNTIME_POLICY_SCHEMA_VERSION = \"caveman.runtime-policy.v1\";\n\n/** Runtime-policy envelopes are KB-sized; stop a hostile body after 1 MiB. */\nconst RUNTIME_POLICY_MAX_RESPONSE_BYTES = 1024 * 1024;\n\nasync function readRuntimePolicyResponseText(response: Response): Promise<string> {\n  const contentLength = response.headers?.get?.(\"content-length\");\n  if (typeof contentLength === \"string\" && /^[0-9]+$/.test(contentLength) && Number(contentLength) > RUNTIME_POLICY_MAX_RESPONSE_BYTES) {\n    await Promise.resolve(response.body?.cancel?.()).catch(() => undefined);","sourceCodeStart":842,"sourceCodeEnd":878,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L842-L878","documentation":"`trace.checkpoints.expand(sourceRef)` requires the expand endpoint to return a non-empty `source_ref` string and a `messages` array; both are mandatory because a checkpoint that cannot be expanded is treated as a bug. The library throws CaveRequestError when the response violates this contract.","triggerScenarios":"Calling expand with a sourceRef whose server response lacks/empties source_ref or omits messages (not an array).","commonSituations":"Backend version mismatch with an older expand endpoint; corrupted or foreign checkpoint reference; proxy altering the response body.","solutions":["Verify the checkpoint sourceRef is valid and was created by a compatible backend version","Check backend version supports checkpoint expand returning source_ref + messages","Catch CaveRequestError and handle the un-expandable checkpoint (rebuild conversation state)"],"exampleFix":"// before\nconst { messages } = await trace.checkpoints.expand(ref);\n// after\nlet expanded;\ntry {\n  expanded = await trace.checkpoints.expand(ref);\n} catch (e) {\n  if (e instanceof CaveRequestError && e.message.includes(\"source_ref\")) {\n    return rebuildMessagesFromScratch();\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const looksExpandable = (r) => typeof r?.source_ref === \"string\" && r.source_ref.trim() !== \"\" && Array.isArray(r.messages);","typeGuard":"function isCheckpointExpandResponse(r: unknown): r is { source_ref: string; messages: unknown[] } {\n  return typeof r === \"object\" && r !== null &&\n    typeof (r as any).source_ref === \"string\" && (r as any).source_ref.trim() !== \"\" &&\n    Array.isArray((r as any).messages);\n}","tryCatchPattern":"try {\n  return await trace.checkpoints.expand(ref);\n} catch (e) {\n  if (e instanceof CaveRequestError && e.message.includes(\"source_ref\")) {\n    return rebuildConversationState(ref);\n  }\n  throw e;\n}","preventionTips":["Only expand checkpoints created by a compatible backend version","Validate refs before expanding (format, origin trace)","Add an integration test covering the expand round-trip"],"tags":["typescript","checkpoints","api-response","validation"],"backgroundTag":"unexpected-api-response-shape","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"}