{"record":{"id":"9b393ae623f0ab4a","repo":"JuliusBrussee/caveman","slug":"cave-tool-schema-invalid","errorCode":"cave_tool_schema_invalid","errorMessage":"cave_tool_schema_invalid:${definition.name}","messagePattern":"cave_tool_schema_invalid:(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":3821,"sourceCode":"      typeof parsed.description !== \"string\" || !isRecord(parsed.input)) {\n    throw new Error(`tool_schema_transform_invalid:${segmentID}`);\n  }\n  return output;\n}\n\nfunction providerToolDefinition(\n  definition: ToolDefinition,\n  lowered: LoweredContext,\n  appliedPlan: AppliedPlan,\n): { description: string; input: TSchema } {\n  const segment = lowered.ir.segments.find((item) => item.id === `tool.${definition.name}`);\n  if (!segment) throw new Error(`cave_context_segment_missing:tool.${definition.name}`);\n  const body = appliedPlan.bodies.get(segment.bodyHandle);\n  if (!body) throw new Error(`cave_context_body_missing:tool.${definition.name}`);\n  const parsed = JSON.parse(new TextDecoder().decode(body)) as unknown;\n  if (!isRecord(parsed) || parsed.name !== definition.name ||\n      typeof parsed.description !== \"string\" || !isRecord(parsed.input)) {\n    throw new Error(`cave_tool_schema_invalid:${definition.name}`);\n  }\n  return { description: parsed.description, input: parsed.input as TSchema };\n}\n\nfunction admitSubagent(state: InvocationState): () => void {\n  const ledger = state.ledger;\n  if (ledger.maxInvocations !== undefined && ledger.admitted >= ledger.maxInvocations) {\n    ledger.invocationRejections++;\n    throw new Error(\"cave_subagent_invocation_limit\");\n  }\n  if (ledger.maxConcurrent !== undefined && ledger.active >= ledger.maxConcurrent) {\n    ledger.concurrencyRejections++;\n    throw new Error(\"cave_subagent_concurrency_limit\");\n  }\n  ledger.admitted++;\n  ledger.active++;\n  ledger.peakActive = Math.max(ledger.peakActive, ledger.active);\n  let released = false;","sourceCodeStart":3803,"sourceCodeEnd":3839,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L3803-L3839","documentation":"The tool's body bytes, once parsed, must be a record whose \"name\" equals the definition name, with a string \"description\" and a record \"input\" schema. Otherwise the runtime declares the stored tool schema invalid and includes the tool name in the message.","triggerScenarios":"JSON.parse of the segment body yields a non-record, a mismatched name, a non-string description, or a non-record input at runtime.ts:3821.","commonSituations":"A custom tool serializer writing a different JSON shape; a tool renamed in one place but not the other; hand-authored tool bodies with missing fields; a transform encoding the schema under a different key.","solutions":["Fix the tool body to the expected shape: { name, description, input } with name matching the registered definition.","Ensure the serializer that produces segment bodies emits exactly these fields.","Compare the parsed body against the definition to find the mismatching field.","If a transform rewrote the body, disable or correct that transform."],"exampleFix":"// before: mismatched name in stored body\n{ \"name\": \"SearchTool\", \"description\": \"...\", \"input\": {} }\n// after: name matches definition.name\n{ \"name\": \"search\", \"description\": \"...\", \"input\": { \"type\": \"object\" } }","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(new TextDecoder().decode(body));\nconst ok = parsed && typeof parsed === 'object' &&\n  parsed.name === definition.name &&\n  typeof parsed.description === 'string' &&\n  parsed.input && typeof parsed.input === 'object';\nif (!ok) throw new Error(`tool body for ${definition.name} must be { name, description, input }`);","typeGuard":"function validBody(v: unknown, name: string): v is { name: string; description: string; input: Record<string, unknown> } {\n  const r = v as Record<string, unknown> | null;\n  return !!r && typeof r === 'object' && r.name === name && typeof r.description === 'string' && !!r.input && typeof r.input === 'object';\n}","tryCatchPattern":"try {\n  return providerToolDefinition(def, lowered, plan);\n} catch (e) {\n  if (String(e?.message).startsWith('cave_tool_schema_invalid')) {\n    console.error(`stored schema for ${def.name} is malformed; regenerate bodies`);\n  }\n  throw e;\n}","preventionTips":["Use one shared serializer for tool bodies so the shape never drifts.","Keep definition.name and the serialized body name in sync (single source of truth).","Add a registry-level schema check when tools are registered."],"tags":["schema-validation","tool-schema","serialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}