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

  1. Set effect to exactly one of "read", "write", "idempotent", or "external"
  2. Map legacy/foreign effect values to the closest allowed one before calling tool()
  3. 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

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


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.inputJSONSchema

View on GitHub (pinned to 3ee70a1026)