JuliusBrussee/caveman · error

caveman build: config must use strict lock and required…

Error message

caveman build: config must use strict lock and required sandbox

What it means

loadBuildInputs imports the caveman config and enforces that every build uses lock: "strict" and sandbox: "required". These are non-negotiable safety invariants of the caveman build system — a config relaxing either is rejected before any agent loading happens. The error fires when the config is missing entirely, or when either field has a different value.

Solutions

  1. Set `lock: "strict"` and `sandbox: "required"` in the defineBuild() call in caveman.config.ts.
  2. If you copied an old config, update it to the current schema and re-run.
  3. Ensure the config actually exports via default or `config` so the object is seen.
  4. Re-run `npm run build` after fixing.

Example fix

// before
export default defineBuild({ lock: "loose", sandbox: "optional", evals: "evals/*.eval.ts", entry: "src/agent.ts" });
// after
export default defineBuild({ lock: "strict", sandbox: "required", evals: "evals/*.eval.ts", entry: "src/agent.ts" });
Defensive patterns

Strategy: validation

Validate before calling

const cfg = (await import("./caveman.config.ts")).default;
if (!cfg || cfg.lock !== "strict" || cfg.sandbox !== "required") {
  throw new Error("caveman.config.ts must set lock: 'strict' and sandbox: 'required'");
}

Type guard

const isStrictConfig = (c: unknown): c is BuildConfig & { lock: "strict"; sandbox: "required" } =>
  typeof c === "object" && c !== null &&
  (c as BuildConfig).lock === "strict" && (c as BuildConfig).sandbox === "required";

Try / catch

try {
  await loadBuildInputs(root, configPath);
} catch (err) {
  if (String(err?.message).includes("strict lock and required sandbox")) {
    console.error("Update caveman.config.ts: defineBuild({ lock: 'strict', sandbox: 'required', ... })");
  }
  throw err;
}

Prevention

When it happens

Trigger: caveman.config.ts omits `lock` or `sandbox`, sets `lock: "loose"|"off"` or `sandbox: "optional"|"disabled"`, exports a non-object, or uses an old config shape without these fields.

Common situations: Following an outdated tutorial/template that set sandbox to optional; disabling the sandbox locally for debugging and forgetting to revert; hand-written config missing the fields; defineBuild defaults that don't satisfy the strict requirement.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

  "package.json",
  "package-lock.json",
  "npm-shrinkwrap.json",
  "pnpm-lock.yaml",
  "yarn.lock",
] as const;

async function loadBuildInputs(
  root: string,
  configPath: string,
  beforeSourceHash?: (
    inputs: Omit<LoadedBuildInputs, "sourceSha256">,
  ) => Promise<void>,
): Promise<LoadedBuildInputs> {
  const configAbsolute = resolve(root, configPath);
  const imported = await importFresh(configAbsolute) as { default?: BuildConfig; config?: BuildConfig };
  const config = imported.default ?? imported.config;
  if (!config || config.lock !== "strict" || config.sandbox !== "required") {
    throw new Error("caveman build: config must use strict lock and required sandbox");
  }
  const entryAbsolute = resolve(root, config.entry);
  const agent = await loadAgent(entryAbsolute);
  const evalFiles: string[] = [];
  for await (const path of glob(config.evals, { cwd: root })) evalFiles.push(resolve(root, path));
  evalFiles.sort();
  const evals: EvalDefinition[] = [];
  for (const path of evalFiles) {
    const module = await importFresh(path) as Record<string, unknown>;
    for (const value of Object.values(module)) {
      if (isEval(value)) evals.push(value);
    }
  }
  await beforeSourceHash?.({ config, agent, evals });
  const sourceFiles = new Set<string>([configAbsolute, entryAbsolute, ...evalFiles]);
  for (const pattern of SOURCE_PATTERNS) {
    for await (const path of glob(pattern, { cwd: root })) sourceFiles.add(resolve(root, path));
  }

View on GitHub (pinned to 3ee70a1026)