{"record":{"id":"a1e891ab26fc91c2","repo":"JuliusBrussee/caveman","slug":"cave-context-body-missing-segment-id","errorCode":null,"errorMessage":"cave_context_body_missing:${segment.id}","messagePattern":"cave_context_body_missing:(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/cli.ts","lineNumber":854,"sourceCode":"      return \"run caveman login, then npm run build\";\n    case \"search_budget_exceeded\":\n      return \"raise maxSearchCostUsd or narrow allowed models, then run npm run build\";\n    case \"no_passing_build\":\n      return \"keep baseline and inspect failing eval evidence\";\n    case \"incomplete_evidence\":\n      return \"fix missing terminal usage or grader evidence, then run npm run build\";\n  }\n}\n\nasync function profilePreferredTransforms(\n  lowered: Awaited<ReturnType<typeof lowerContext>>,\n): Promise<ReadonlyMap<string, string>> {\n  const preferred = new Map<string, string>();\n  for (const segment of lowered.ir.segments) {\n    if (segment.safety !== \"S4\" ||\n        /(?:opaque|signed|signature|jwt|token|cipher|encrypted)/i.test(segment.id)) continue;\n    const body = lowered.bodies.get(segment.bodyHandle);\n    if (!body) throw new Error(`cave_context_body_missing:${segment.id}`);\n    const type = await detectEngineType(body);\n    if (/^[a-z0-9-]+$/.test(type) && type !== \"unknown\") {\n      preferred.set(segment.id, `caveman.engine.${type}.v1`);\n    }\n  }\n  return preferred;\n}\n\nasync function detectEngineType(input: Uint8Array): Promise<string> {\n  const command = process.env.CAVEMAN_ENGINE_BIN ?? \"caveman-engine\";\n  return new Promise((accept, reject) => {\n    const env = buildEngineEnv();\n    const invocation = portableInvocation(command, [\"detect\"], { env });\n    const child = spawn(invocation.command, [...invocation.args], {\n      env,\n      stdio: [\"pipe\", \"pipe\", \"pipe\"],\n    });\n    const stdout: Buffer[] = [];","sourceCodeStart":836,"sourceCodeEnd":872,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/cli.ts#L836-L872","documentation":"Thrown in `profilePreferredTransforms` while probing engine types for preferred transforms. For every segment marked `safety !== \"S4\"` whose id does not look opaque (no signed/jwt/token/cipher markers), the function looks up the segment's body via `lowered.bodies.get(segment.bodyHandle)`. If the IR references a bodyHandle that has no entry in the bodies map, the CLI throws `cave_context_body_missing:<segment.id>` with the offending segment id interpolated.","triggerScenarios":"`caveman build` / `profilePreferredTransforms(lowered)` where `lowerContext` produced an IR whose segment.bodyHandle is absent from `lowered.bodies`. This happens when a segment is constructed with a handle string that was never registered, a body was garbage-collected or deduplicated away, or a custom context source emits its own handles.","commonSituations":"Custom context plugins that fabricate bodyHandle values; concurrent modification of the lowered structure between lowering and profiling; a framework upgrade that changed body-handle generation without rebuilding cached lowered contexts; fixture segments in tests referencing removed bodies.","solutions":["Take the segment id from the message and inspect that segment's bodyHandle — it will not be a key in the bodies map.","Re-run full lowering (`lowerBuildContext`) in the same process instead of reusing a stale lowered result, so IR and bodies stay paired.","If you own the context source, register every body through the same store that produces handles so the pair is atomic.","Report a framework bug if a plain `agent()` definition with only built-in context sources triggers it."],"exampleFix":"// before: handle never registered\nir.segments.push({ id: \"sys\", bodyHandle: \"cave_local_sha256:deadbeef...\" });\n\n// after: put the body first, use the returned handle\nconst handle = await bodies.put(bytes);\nir.segments.push({ id: \"sys\", bodyHandle: handle });","handlingStrategy":"validation","validationCode":"function allBodiesPresent(lowered: { ir: { segments: Array<{ id: string; bodyHandle: string }> }, bodies: Map<string, unknown> }): boolean {\n  return lowered.ir.segments.every((s) => lowered.bodies.has(s.bodyHandle));\n}\n// call after lowerContext and before handing the result onward","typeGuard":"function segmentHasBody(segment: { bodyHandle: string }, bodies: Map<string, unknown>): boolean {\n  return bodies.has(segment.bodyHandle);\n}","tryCatchPattern":"try {\n  await build(args);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"cave_context_body_missing:\")) {\n    const segmentId = error.message.split(\":\")[1]; // re-lower the context and inspect that segment\n  } else throw error;\n}","preventionTips":["Treat IR and bodies as one atomic unit; never mutate one without the other.","Always call lowerContext fresh in the same process instead of caching half of its result.","Register bodies through the same store that mints handles."],"tags":["context-ir","build","internal-consistency"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}