{"record":{"id":"13ec1893e15c40c0","repo":"JuliusBrussee/caveman","slug":"cave-mastra-max-steps-invalid","errorCode":"cave_mastra_max_steps_invalid","errorMessage":"cave_mastra_max_steps_invalid","messagePattern":"cave_mastra_max_steps_invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":302,"sourceCode":"    error?: unknown;\n    finishReason?: unknown;\n    response?: { modelId?: unknown };\n  }>;\n}\n\nexport interface MastraAdapterOptions {\n  /** Mastra's per-generate maximum number of sequential LLM calls. */\n  maxSteps?: number;\n}\n\nexport function createMastraAdapter(\n  identity: HarnessAdapterIdentity,\n  agent: MastraAgentBinding,\n  options: MastraAdapterOptions = {},\n): HarnessAdapter {\n  const maxSteps = options.maxSteps;\n  if (maxSteps !== undefined && (!Number.isSafeInteger(maxSteps) || maxSteps <= 0)) {\n    throw new Error(\"cave_mastra_max_steps_invalid\");\n  }\n  assertSupportedUpstream(identity, MASTRA_VERSION, \"mastra\", \"@mastra/core\");\n  return createHarnessAdapter(\"mastra\", identity, {\n    package: \"@mastra/core/agent\",\n    class: \"Agent\",\n    method: \"generate\",\n    usage: \"FullOutput.totalUsage\",\n    ...(maxSteps === undefined ? {} : { preExecutionControls: { maxSteps } }),\n  }, async (request) => {\n    const startedAt = performance.now();\n    const response = await agent.generate(request.prompt, {\n      maxProcessorRetries: 0,\n      runId: request.runID,\n      ...(maxSteps === undefined ? {} : { maxSteps }),\n      ...(request.signal === undefined ? {} : { abortSignal: request.signal }),\n    });\n    if (response.error !== undefined || terminalFinishReason(response.finishReason) === false) {\n      throw new Error(\"cave_mastra_terminal_failure\");","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/adapters.ts#L284-L320","documentation":"extractTools iterates the parsed list, keeps only items that are JSON objects, unwraps OpenAI-style 'function' nesting, and requires a non-empty string 'name'. If no entry yields a name — every item was a non-object, a scalar array like [1,2,3], an array of strings, or objects missing/blank 'name' — it returns 'no named tools found'. The document parsed and had the right top-level shape, but contained zero recognizable tools.","triggerScenarios":"Piping an empty array ([]), an array of strings ([\"tool_a\",\"tool_b\"]), objects whose name is missing/blank/non-string (e.g. {name: 123}), or an object with neither 'tools' nor 'functions' keys and no top-level 'name' of its own.","commonSituations":"Fetching from the wrong endpoint that returns an unrelated object; a catalog filtered server-side to zero tools; name fields under a different key (\"toolName\", \"id\") than the expected \"name\"; OpenAI catalogs where the function wrapper lost its nested object.","solutions":["Inspect the input: jq 'map(has(\"name\"))' on arrays, or jq 'keys' on objects — confirm a 'name' string exists per tool.","Fix the producer/wrapper to emit the standard shapes: MCP {\"tools\":[{\"name\":...}]}, OpenAI [{\"function\":{\"name\":...}}], or {\"functions\":[{\"name\":...}]}.","If tools legitimately use a different name key, normalize it before piping: jq 'map(.toolName as $n | .name = $n)'.","Handle empty catalogs upstream — decide whether an empty tool list should skip the shrink entirely."],"exampleFix":"// before: catalog with non-standard name key\n{\"tools\":[{\"toolName\":\"read_file\",\"inputSchema\":{...}}]}\n\n// after: normalized before piping\n// jq '.tools |= map(.name = (.name // .toolName))' catalog.json | caveman-shrink\n{\"tools\":[{\"name\":\"read_file\",\"inputSchema\":{...}}]}","handlingStrategy":"type-guard","validationCode":"func hasNamedTools(b []byte) bool {\n    var top any\n    if json.Unmarshal(b, &top) != nil {\n        return false\n    }\n    var list []any\n    switch t := top.(type) {\n    case []any:\n        list = t\n    case map[string]any:\n        if v, ok := t[\"tools\"].([]any); ok {\n            list = v\n        } else if v, ok := t[\"functions\"].([]any); ok {\n            list = v\n        } else {\n            list = []any{t}\n        }\n    }\n    for _, item := range list {\n        obj, ok := item.(map[string]any)\n        if !ok {\n            continue\n        }\n        if fn, ok := obj[\"function\"].(map[string]any); ok {\n            obj = fn\n        }\n        if n, _ := obj[\"name\"].(string); n != \"\" {\n            return true\n        }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"entries, err := extractTools(input)\nif err != nil {\n    if strings.Contains(err.Error(), \"no named tools found\") {\n        // catalog parsed but had zero tools: skip shrink or alert on the producer\n    }\n    return err\n}","preventionTips":["Assert each catalog entry has a non-empty string 'name' (or nested 'function.name') before piping.","Normalize non-standard name keys (toolName, id) with jq before shrink.","Treat empty tool catalogs as an expected case: short-circuit pipelines instead of erroring."],"tags":["json","api-contract","tools","input-validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}