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

  1. Take the segment id from the message and inspect that segment's bodyHandle — it will not be a key in the bodies map.
  2. Re-run full lowering (`lowerBuildContext`) in the same process instead of reusing a stale lowered result, so IR and bodies stay paired.
  3. If you own the context source, register every body through the same store that produces handles so the pair is atomic.
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/a1e891ab26fc91c2. Report an issue: GitHub.