JuliusBrussee/caveman · error
cave_internal_run_option
cave_internal_run_option
Error message
cave_internal_run_option: Cave Build execution is available through caveman-agent dev/build after lock validation
What it means
Public run entry points reject RunOptions carrying session-internal fields: buildIdentity, efficiencyPlan, lockedBuild, candidatePlan, caveRoute, invocationTrace, invocationState. Those fields drive Cave Build execution, which is reachable only through caveman-agent dev/build after lock validation; the guard runs before any session-internal field is merged so a caller cannot forge plan/routing identity into a public call. The message states where the capability actually lives.
Source
Thrown at packages/agent/src/runtime.ts:5003
}
}
return false;
}
/**
* Refuse caller-supplied build identity, plan, or routing.
*
* Exported for the other public entry points that forward caller options into
* `runAgentInternal` (`./code`): the guard belongs to whoever accepts the
* untrusted object, and it must run BEFORE any session-internal field is merged
* in, or the session's own plan and route would trip it.
*/
export function rejectInternalRunOptions(options: RunOptions): void {
const value = options as Record<string, unknown>;
if ("buildIdentity" in value || "efficiencyPlan" in value ||
"lockedBuild" in value || "candidatePlan" in value || "caveRoute" in value ||
"invocationTrace" in value || "invocationState" in value) {
throw new Error(
"cave_internal_run_option: Cave Build execution is available through caveman-agent dev/build after lock validation",
);
}
}
function sandboxDependencyReadRoots(): string[] {
const resolved = [
import.meta.resolve("@earendil-works/pi-agent-core"),
import.meta.resolve("@earendil-works/pi-ai"),
].map((url) => fileURLToPath(url));
const roots = new Set<string>();
for (const path of resolved) {
const pnpm = path.indexOf("/node_modules/.pnpm/");
if (pnpm >= 0) {
roots.add(path.slice(0, pnpm + "/node_modules/.pnpm".length));
} else {
roots.add(dirname(dirname(path)));
}View on GitHub (pinned to 766dce6b13)
Solutions
- Remove the internal fields; build options from your own typed RunOptions literal
- Use the caveman-agent dev/build CLI path for locked-plan execution - it performs lock validation first
- Never round-trip option objects captured from internal sessions into public calls
- If a wrapper proxies fields blindly, filter to a known allow-list of public keys before forwarding
Example fix
// before
await run(agent, { ...capturedInternalOptions, input });
// after: only public fields
await run(agent, { model, signal, maxSubagentDepth: 2, input }); Defensive patterns
Strategy: type-guard
Type guard
// Reject (or strip) session-internal fields before calling public run APIs
const INTERNAL_RUN_OPTION_KEYS = ['buildIdentity', 'efficiencyPlan', 'lockedBuild', 'candidatePlan', 'caveRoute', 'invocationTrace', 'invocationState'] as const;
function isPublicRunOptions(options: Record<string, unknown>): boolean {
return !INTERNAL_RUN_OPTION_KEYS.some((key) => key in options);
}
function toPublicRunOptions<T extends object>(options: T): T {
const copy: Record<string, unknown> = { ...options };
for (const key of INTERNAL_RUN_OPTION_KEYS) delete copy[key];
return copy as T;
} Try / catch
try {
return await run(agent, options);
} catch (error) {
if (error instanceof Error && error.message.startsWith('cave_internal_run_option')) {
return await run(agent, toPublicRunOptions(options)); // retry with internal fields stripped
}
throw error;
} Prevention
- Construct RunOptions from typed literals; never spread captured or unknown objects into them
- Do not round-trip option objects observed inside sessions back into public calls
- Use the caveman-agent dev/build CLI for locked-plan execution - it performs lock validation
- In wrappers, forward an explicit allow-list of public keys instead of proxying every field
When it happens
Trigger: Passing an options object containing any of the seven internal keys to a public run/invoke API; spreading a previously captured internal options object (e.g., observed from a session turn) back into a public call; trying to hand-unlock locked-build execution by constructing a lockedBuild/candidatePlan value.
Common situations: Spreading unknown or round-tripped objects into options ({...captured, input}); copying example/test fixtures that included internal fields; upgrading to a version where previously-ignored extra fields became a hard rejection; SDK wrappers that proxy every field through.
Related errors
- cave_sandbox_network_egress_unbounded
- cave_harness_model_identity_missing
- cave_live_eval_sandbox_profile_escapes_root
- caveman-code: path escapes the workspace: ${candidate}
- cave_runtime_segment_id_collision
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/f4087f18146a29aa.
Report an issue: GitHub.