{"record":{"id":"7f673f636c29961e","repo":"JuliusBrussee/caveman","slug":"caveman-agent-unknown-tool-effect-json-stringif","errorCode":null,"errorMessage":"caveman agent: unknown tool effect ${JSON.stringify(options.effect)}","messagePattern":"caveman agent: unknown tool effect (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/primitives.ts","lineNumber":132,"sourceCode":"export function tool<TInput extends TSchema, TResult>(\n  options: ToolOptions<TInput, TResult>,\n): ToolDefinition<Static<TInput>, TResult>;\nexport function tool<Input, Output, TResult>(\n  options: StandardToolOptions<Input, Output, TResult>,\n): ToolDefinition<Output, TResult>;\nexport function tool<Input, Output, TResult>(\n  options: StandardJSONToolOptions<Input, Output, TResult>,\n): ToolDefinition<Output, TResult>;\nexport function tool(\n  options: ToolOptions<TSchema, unknown> |\n    StandardToolOptions<unknown, unknown, unknown> |\n    StandardJSONToolOptions<unknown, unknown, unknown>,\n): ToolDefinition<unknown, unknown> {\n  if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(options.name)) {\n    throw new Error(`caveman agent: invalid tool name ${JSON.stringify(options.name)}`);\n  }\n  if (![\"read\", \"write\", \"idempotent\", \"external\"].includes(options.effect)) {\n    throw new Error(`caveman agent: unknown tool effect ${JSON.stringify(options.effect)}`);\n  }\n  const result = typeof options.result === \"object\"\n    ? artifactResultPolicy(options.result)\n    : options.result ?? \"auto\";\n  if (![\"auto\", \"inline\", \"page\", \"compress\", \"exact_ccr\"].includes(result)) {\n    throw new Error(`caveman agent: unknown tool result policy ${JSON.stringify(result)}`);\n  }\n  const timeoutMs = options.timeoutMs ?? 30_000;\n  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {\n    throw new Error(\"caveman agent: tool timeoutMs must be a positive integer\");\n  }\n  const standard = standardToolSchema(options.input);\n  let input: TSchema;\n  if (standard === undefined) {\n    input = options.input as TSchema;\n  } else {\n    let converted = \"inputJSONSchema\" in options\n      ? options.inputJSONSchema","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/primitives.ts#L114-L150","documentation":"tool() requires an effect of exactly one of \"read\", \"write\", \"idempotent\", or \"external\" — the effect taxonomy the runtime uses for planning, replay, and permission decisions. Any other string (including casing variants like \"Read\", typos like \"readonly\", or \"side-effect\") throws immediately with the invalid value embedded.","triggerScenarios":"Passing effect: \"readonly\", \"Read\", \"pure\", \"mutation\", \"io\", or undefined. The field is mandatory with no default; omitting it entirely surfaces as an invalid undefined value in this same error.","commonSituations":"Migrating from another agent framework whose effect vocabulary differs (e.g. \"destructive\"/\"safe\"), assuming case-insensitivity, or optional-spread code paths that accidentally drop the field.","solutions":["Use one of the four exact literals: \"read\", \"write\", \"idempotent\", \"external\"","Pick by side-effect profile: no state change -> \"read\"; repeated same-call is safe -> \"idempotent\"; state changes and is not safely repeatable -> \"write\"; external system interaction -> \"external\"","If migrating, map old vocabularies explicitly through a lookup table with a fallback that throws your own descriptive error"],"exampleFix":"// before\nconst t = tool({ name: \"save\", effect: \"readonly\", execute: ... });\n\n// after\nconst t = tool({ name: \"save\", effect: \"write\", execute: ... });","handlingStrategy":"type-guard","validationCode":"const EFFECTS = [\"read\", \"write\", \"idempotent\", \"external\"] as const;\nif (!EFFECTS.includes(cfg.effect)) {\n  throw new Error(`effect must be one of ${EFFECTS.join(\", \")}, got ${JSON.stringify(cfg.effect)}`);\n}","typeGuard":"type ToolEffect = \"read\" | \"write\" | \"idempotent\" | \"external\";\nconst isToolEffect = (v: unknown): v is ToolEffect =>\n  typeof v === \"string\" && [\"read\", \"write\", \"idempotent\", \"external\"].includes(v);","tryCatchPattern":null,"preventionTips":["Type your tool option objects with the literal union so the compiler rejects bad effects","When migrating frameworks, map effect vocabularies through an explicit table","Never make effect optional in your own tool-building helpers"],"tags":["validation","tool-effects","api-misuse","typescript"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}