JuliusBrussee/caveman · error
caveman agent: unknown tool effect
Error message
caveman agent: unknown tool effect ${JSON.stringify(options.effect)} What it means
tool() validates options.effect against the allow-list ["read", "write", "idempotent", "external"]. The effect declares the tool's side-effect class and is used for safety/policy decisions, so an unrecognized value is rejected immediately.
Solutions
- Set effect to exactly one of "read", "write", "idempotent", or "external"
- Map legacy/foreign effect values to the closest allowed one before calling tool()
- Add a compile-time union type ("read" | "write" | "idempotent" | "external") to catch typos at typecheck
Example fix
// before
tool({ name: "deploy", effect: "destructive", ... })
// after
tool({ name: "deploy", effect: "external", ... }) Defensive patterns
Strategy: type-guard
Validate before calling
type Effect = "read" | "write" | "idempotent" | "external";
const EFFECTS: readonly Effect[] = ["read", "write", "idempotent", "external"];
function isEffect(v: unknown): v is Effect {
return typeof v === "string" && (EFFECTS as readonly string[]).includes(v);
} Type guard
function isEffect(v: unknown): v is "read" | "write" | "idempotent" | "external" {
return ["read", "write", "idempotent", "external"].includes(v as never);
} Try / catch
try {
const def = tool(options);
} catch (err) {
if (err instanceof Error && err.message.includes("unknown tool effect")) {
// map options.effect to an allowed value and retry
} else throw err;
} Prevention
- Always use the Effect union type instead of raw strings
- Map third-party effect vocabularies explicitly before calling tool()
- Keep effect values in one shared constant list
When it happens
Trigger: tool({ effect: "readonly" }) instead of "read"; tool({ effect: "mutate" }) instead of "write"; misspellings like "side-effect"; effect left undefined when the options shape requires it.
Common situations: Typos or synonyms when hand-writing tool definitions; migrating from another framework whose effect vocabulary differs (e.g. "pure", "destructive"); conditional code building the effect string produces undefined.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- caveman agent: unknown tool result policy
- assembly slot has unknown stability
- assembly slot has unknown stability
- bedrock endpoint must be runtime or mantle
- cave_duplicate_tool_name
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/7f673f636c29961e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/primitives.ts:132
export function tool<TInput extends TSchema, TResult>(
options: ToolOptions<TInput, TResult>,
): ToolDefinition<Static<TInput>, TResult>;
export function tool<Input, Output, TResult>(
options: StandardToolOptions<Input, Output, TResult>,
): ToolDefinition<Output, TResult>;
export function tool<Input, Output, TResult>(
options: StandardJSONToolOptions<Input, Output, TResult>,
): ToolDefinition<Output, TResult>;
export function tool(
options: ToolOptions<TSchema, unknown> |
StandardToolOptions<unknown, unknown, unknown> |
StandardJSONToolOptions<unknown, unknown, unknown>,
): ToolDefinition<unknown, unknown> {
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {
throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);
}
if (!["read", "write", "idempotent", "external"].includes(options.effect)) {
throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);
}
const result = typeof options.result === "object"
? artifactResultPolicy(options.result)
: options.result ?? "auto";
if (!["auto", "inline", "page", "compress", "exact_ccr"].includes(result)) {
throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);
}
const timeoutMs = options.timeoutMs ?? 30_000;
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
throw new Error("caveman agent: tool timeoutMs must be a positive integer");
}
const standard = standardToolSchema(options.input);
let input: TSchema;
if (standard === undefined) {
input = options.input as TSchema;
} else {
let converted = "inputJSONSchema" in options
? options.inputJSONSchemaView on GitHub (pinned to 3ee70a1026)