JuliusBrussee/caveman · critical · Error

cave_runtime_segment_id_collision

cave_runtime_segment_id_collision

Error message

cave_runtime_segment_id_collision

What it means

When a runtime segment is lowered into the Context IR, segment identity is content-derived: if a segment id already exists, it must carry the same sha256 digest. The same id with different bytes means the id was reused for new content — exactly the confusion that once let compaction substitute an OLD message's compressed body into a NEW message — so the library fails closed instead of poisoning the CCR restore map.

Source

Thrown at packages/agent/src/context-ir.ts:287

  return { ir: { schemaVersion: 1, segments }, bodies };
}

export function appendRuntimeContextSegment(
  lowered: LoweredContext,
  segment: RuntimeContextSegment,
): ContextSegment {
  const digest = sha256(segment.body);
  const existing = lowered.ir.segments.find((item) => item.id === segment.id);
  if (existing) {
    // Segment identity is content-derived, so the same id must
    // carry the same bytes. If it does not, the id has been reused for
    // different content — the exact confusion that once let compaction
    // substitute an OLD message's compressed body into a NEW message. Fail
    // closed rather than returning the stale segment and poisoning the CCR
    // restore map.
    if (existing.provenanceDigest !== digest) {
      throw new Error("cave_runtime_segment_id_collision");
    }
    return existing;
  }
  const bodyHandle = `cave_local_sha256:${digest}`;
  (lowered.bodies as Map<string, Uint8Array>).set(bodyHandle, segment.body.slice());
  const appended: ContextSegment = {
    id: segment.id,
    kind: segment.kind,
    stability: "turn",
    safety: "S4",
    priority: "normal",
    recovery: "exact_ccr",
    cacheRegion: "live_zone",
    privacy: "local_sensitive",
    opaque: opaquePayload(segment.body),
    provenanceDigest: digest,
    tokenCount: estimateTokens(segment.body),
    bodyHandle,

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Derive ids from content, e.g. include the digest: `id: 'tool-result-' + digest.slice(0, 16)`.
  2. Never mutate a segment's body after adding it; add a new segment with a new id instead.
  3. Start a fresh lowered context when content identity changes rather than reusing ids.

Example fix

// before
const id = `tool-result-${index}`; // same id, new bytes on retry
// after
const digest = sha256(body);
const id = `tool-result-${digest.slice(0, 16)}`; // id changes with content
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await withContextSegments(segments); // lowering/addition of runtime segments
} catch (error) {
  if (error instanceof Error && error.message === "cave_runtime_segment_id_collision") {
    throw new Error(
      "segment id reused for different content: regenerate ids from the content digest",
    );
  }
  throw error;
}

Prevention

When it happens

Trigger: Appending a segment whose id matches an existing one while its body changed: sequentially generated ids like `tool-result-1` reused after content updates, or a retry that regenerates output under the same id within one context build.

Common situations: Custom id schemes that aren't content-derived; caching layers that reuse keys across content versions; mutating a segment body after it was already lowered into the IR.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/38f4499a967e878a. Report an issue: GitHub.