JuliusBrussee/caveman · error · Error

cave_transform_registry_unavailable: run caveman setup or…

Error message

cave_transform_registry_unavailable: run caveman setup or set CAVEMAN_ENGINE_BIN (${error instanceof Error ? error.message : String(error)})

What it means

`loadTransformRegistry` shells out to the engine binary (`process.env.CAVEMAN_ENGINE_BIN ?? "caveman-engine"`) with args `["registry"]`, a 10-second timeout and 2 MiB maxBuffer, then requires stdout to be JSON with a 64-hex `registry_sha256` and a non-empty `capabilities` array of well-formed entries. Every failure mode — binary not found (spawn ENOENT), timeout, non-zero exit, invalid JSON, bad shape — is wrapped into this single error with the underlying message appended in parentheses.

Solutions

  1. Run `caveman setup` to install/bootstrap the engine
  2. Set CAVEMAN_ENGINE_BIN to the absolute path of a working engine binary
  3. Verify manually: `$CAVEMAN_ENGINE_BIN registry` must print JSON with a 64-hex registry_sha256 and non-empty capabilities within 10 s
  4. In CI, cache the engine install and export CAVEMAN_ENGINE_BIN in the job environment

Example fix

# before
export CAVEMAN_ENGINE_BIN=engine   # not on PATH → spawn ENOENT
# after
npx caveman setup                    # installs engine and prints its path
export CAVEMAN_ENGINE_BIN=/usr/local/bin/caveman-engine
Defensive patterns

Strategy: validation

Validate before calling

import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const bin = process.env.CAVEMAN_ENGINE_BIN ?? "caveman-engine";
// Fail fast with a clear message before the build wraps it:
const { stdout } = await execFileAsync(bin, ["registry"], { timeout: 10_000 });
const parsed = JSON.parse(stdout); // must contain 64-hex registry_sha256 + capabilities[]

Try / catch

try { await run("npm run build"); } catch (e) {
  if (e instanceof Error && e.message.startsWith("cave_transform_registry_unavailable")) {
    // underlying cause is in the parenthesized suffix; remedy: caveman setup or set CAVEMAN_ENGINE_BIN, then retry once
  } else throw e;
}

Prevention

When it happens

Trigger: `caveman build`/`check`/`dev` (anything reaching `transformRegistrySHA256()`) on a machine where the engine binary is not installed or not on PATH; CAVEMAN_ENGINE_BIN pointing at a wrong or non-executable path; the engine `registry` subcommand emitting malformed JSON, zero capabilities, or timing out past 10 s.

Common situations: Fresh clone without `caveman setup`; CI image with a stripped PATH; engine binary updated so registry output changed; typo in CAVEMAN_ENGINE_BIN.

Related errors


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

Appendix: source

Thrown at packages/agent/src/cli.ts:1132

      throw new Error("registry output has no valid registry_sha256");
    }
    if (!Array.isArray(parsed.capabilities) || parsed.capabilities.length === 0) {
      throw new Error("registry output has no capabilities");
    }
    const capabilities = parsed.capabilities.map((capability): TransformCapability => {
      if (typeof capability.transform_id !== "string" ||
          !Array.isArray(capability.eligible_segment_kinds) ||
          capability.eligible_segment_kinds.some((kind) => typeof kind !== "string")) {
        throw new Error("registry output has invalid capability");
      }
      return {
        transformID: capability.transform_id,
        segmentKinds: capability.eligible_segment_kinds as TransformCapability["segmentKinds"],
      };
    });
    return { sha256: parsed.registry_sha256, capabilities };
  } catch (error) {
    throw new Error(
      `cave_transform_registry_unavailable: run caveman setup or set CAVEMAN_ENGINE_BIN (${error instanceof Error ? error.message : String(error)})`,
    );
  }
}

async function engineEntitled(): Promise<boolean> {
  const command = process.env.CAVEMAN_CLI_BIN ?? "caveman";
  try {
    const env = buildRuntimeControlEnv();
    const invocation = portableInvocation(command, ["status", "--json"], { env });
    const { stdout } = await execFileAsync(invocation.command, [...invocation.args], {
      encoding: "utf8",
      env,
      maxBuffer: 2 << 20,
      timeout: 10_000,
    });
    const parsed = JSON.parse(stdout) as { mode?: unknown; seat?: { entitled?: unknown } };
    return parsed.seat?.entitled === true && parsed.mode === "compress";

View on GitHub (pinned to 766dce6b13)