JuliusBrussee/caveman · error

cave_transform_safety_mismatch

cave_transform_safety_mismatch

Error message

cave_transform_safety_mismatch:${segment.id}

What it means

Thrown when a compaction route targets a segment whose safety classification is not 'S4'. Only S4 (lowest-sensitivity, transform-safe) segments may be rewritten by engine transforms; touching S1–S3 segments is refused to protect sensitive content. The segment id is included in the message.

Solutions

  1. Narrow the plan route with an explicit segment_id that points at an S4 segment.
  2. Check the safety classification assigned during lowering and correct it if it is wrongly restricted.
  3. Remove routes that target non-S4 kinds; route only history/tool_result or S4 context.
  4. If the segment genuinely is safe, update its safety level in the definition/lowering to S4.

Example fix

// before
segment_routes: [{ segment_kind: "context", transform_id: "..." }] // matches S2 segment
// after
segment_routes: [{ segment_kind: "context", segment_id: "docs-s4", transform_id: "..." }]
Defensive patterns

Strategy: validation

Validate before calling

for (const route of plan.segment_routes) {
  const targets = lowered.ir.segments.filter(s => s.kind === route.segment_kind && (route.segment_id === undefined || s.id === route.segment_id));
  const unsafe = targets.filter(s => s.safety !== "S4");
  if (unsafe.length) console.warn(`route would touch non-S4 segments: ${unsafe.map(s => s.id).join(",")}`);
}

Type guard

function isTransformSafe(seg: { safety: string }): boolean { return seg.safety === "S4"; }

Try / catch

try {
  await executePlan(plan, lowered);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("cave_transform_safety_mismatch:")) {
    const id = err.message.split(":")[1];
    // retarget the route to an S4 segment or fix the safety label
  } else throw err;
}

Prevention

When it happens

Trigger: A plan route matches a segment with segment.safety !== "S4" (e.g. instructions or a user-classified context segment) and the runtime attempts to apply the transform to it.

Common situations: Broad segment_kind routes that sweep in higher-safety segments; segments mislabeled S4-eligible during lowering; plans reused across agents where the same kind has different safety labels.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/09fab7ec9dd853cc. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/runtime.ts:2647

  for (const route of plan.segment_routes) {
    if (!known.has(route.transform_id)) {
      throw new Error(`cave_unknown_transform:${route.transform_id}`);
    }
    const targets = lowered.ir.segments.filter((segment) =>
      segment.kind === route.segment_kind &&
      (route.segment_id === undefined || segment.id === route.segment_id));
    if (targets.length === 0) {
      if (route.segment_kind === "history" || route.segment_kind === "tool_result") {
        continue;
      }
      throw new Error(`cave_plan_route_unmatched:${route.segment_id ?? route.segment_kind}`);
    }
    evaluated.push(route.transform_id);
    let routeApplied = false;
    for (const segment of targets) {
      const original = lowered.bodies.get(segment.bodyHandle);
      if (!original) throw new Error(`cave_context_body_missing:${segment.id}`);
      if (segment.safety !== "S4") throw new Error(`cave_transform_safety_mismatch:${segment.id}`);
      const startedAt = performance.now();
      // beforeTokens/afterTokens are byte-derived (bytes/4) throughout so a
      // delta is always within one basis. beforeTokens counts the ORIGINAL
      // bytes; afterTokens, for an applied transform, counts the FULL provider
      // body actually sent — wrapper included. segment.tokenCount
      // is already estimateTokens(original) = bytes/4.
      const beforeTokens = segment.tokenCount;
      if (segment.opaque) {
        trace.push({
          segmentKind: segment.kind,
          transformID: route.transform_id,
          safetyClass: segment.safety,
          beforeTokens,
          afterTokens: beforeTokens,
          tokensBasis: "byte_derived",
          recoveryKind: segment.recovery,
          recoveryUsed: false,
          latencyMs: Math.round(performance.now() - startedAt),

View on GitHub (pinned to 3ee70a1026)