JuliusBrussee/caveman · error · Error
cave_context_body_missing:${segment.id}
Error message
cave_context_body_missing:${segment.id} What it means
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.
Source
Thrown at packages/agent/src/cli.ts:854
return "run caveman login, then npm run build";
case "search_budget_exceeded":
return "raise maxSearchCostUsd or narrow allowed models, then run npm run build";
case "no_passing_build":
return "keep baseline and inspect failing eval evidence";
case "incomplete_evidence":
return "fix missing terminal usage or grader evidence, then run npm run build";
}
}
async function profilePreferredTransforms(
lowered: Awaited<ReturnType<typeof lowerContext>>,
): Promise<ReadonlyMap<string, string>> {
const preferred = new Map<string, string>();
for (const segment of lowered.ir.segments) {
if (segment.safety !== "S4" ||
/(?:opaque|signed|signature|jwt|token|cipher|encrypted)/i.test(segment.id)) continue;
const body = lowered.bodies.get(segment.bodyHandle);
if (!body) throw new Error(`cave_context_body_missing:${segment.id}`);
const type = await detectEngineType(body);
if (/^[a-z0-9-]+$/.test(type) && type !== "unknown") {
preferred.set(segment.id, `caveman.engine.${type}.v1`);
}
}
return preferred;
}
async function detectEngineType(input: Uint8Array): Promise<string> {
const command = process.env.CAVEMAN_ENGINE_BIN ?? "caveman-engine";
return new Promise((accept, reject) => {
const env = buildEngineEnv();
const invocation = portableInvocation(command, ["detect"], { env });
const child = spawn(invocation.command, [...invocation.args], {
env,
stdio: ["pipe", "pipe", "pipe"],
});
const stdout: Buffer[] = [];View on GitHub (pinned to 27d5a3981a)
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.
Example fix
// before: handle never registered
ir.segments.push({ id: "sys", bodyHandle: "cave_local_sha256:deadbeef..." });
// after: put the body first, use the returned handle
const handle = await bodies.put(bytes);
ir.segments.push({ id: "sys", bodyHandle: handle }); Defensive patterns
Strategy: validation
Validate before calling
function allBodiesPresent(lowered: { ir: { segments: Array<{ id: string; bodyHandle: string }> }, bodies: Map<string, unknown> }): boolean {
return lowered.ir.segments.every((s) => lowered.bodies.has(s.bodyHandle));
}
// call after lowerContext and before handing the result onward Type guard
function segmentHasBody(segment: { bodyHandle: string }, bodies: Map<string, unknown>): boolean {
return bodies.has(segment.bodyHandle);
} Try / catch
try {
await build(args);
} catch (error) {
if (error instanceof Error && error.message.startsWith("cave_context_body_missing:")) {
const segmentId = error.message.split(":")[1]; // re-lower the context and inspect that segment
} else throw error;
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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.
Related errors
- cave_privacy_conformance_failed
- caveman build: dataResidency is not enforced yet; refusing t
- caveman build: requiredFixturePassRate must be in (0,1]
- caveman build: qualityRetention must be in (0,1]
- caveman build: config must export defineBuild()
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/a1e891ab26fc91c2.
Report an issue: GitHub.