JuliusBrussee/caveman · error · Error

cave_mastra_max_steps_invalid

cave_mastra_max_steps_invalid

Error message

cave_mastra_max_steps_invalid

What it means

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.

Source

Thrown at packages/agent/src/adapters.ts:302

    error?: unknown;
    finishReason?: unknown;
    response?: { modelId?: unknown };
  }>;
}

export interface MastraAdapterOptions {
  /** Mastra's per-generate maximum number of sequential LLM calls. */
  maxSteps?: number;
}

export function createMastraAdapter(
  identity: HarnessAdapterIdentity,
  agent: MastraAgentBinding,
  options: MastraAdapterOptions = {},
): HarnessAdapter {
  const maxSteps = options.maxSteps;
  if (maxSteps !== undefined && (!Number.isSafeInteger(maxSteps) || maxSteps <= 0)) {
    throw new Error("cave_mastra_max_steps_invalid");
  }
  assertSupportedUpstream(identity, MASTRA_VERSION, "mastra", "@mastra/core");
  return createHarnessAdapter("mastra", identity, {
    package: "@mastra/core/agent",
    class: "Agent",
    method: "generate",
    usage: "FullOutput.totalUsage",
    ...(maxSteps === undefined ? {} : { preExecutionControls: { maxSteps } }),
  }, async (request) => {
    const startedAt = performance.now();
    const response = await agent.generate(request.prompt, {
      maxProcessorRetries: 0,
      runId: request.runID,
      ...(maxSteps === undefined ? {} : { maxSteps }),
      ...(request.signal === undefined ? {} : { abortSignal: request.signal }),
    });
    if (response.error !== undefined || terminalFinishReason(response.finishReason) === false) {
      throw new Error("cave_mastra_terminal_failure");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Inspect the input: jq 'map(has("name"))' on arrays, or jq 'keys' on objects — confirm a 'name' string exists per tool.
  2. Fix the producer/wrapper to emit the standard shapes: MCP {"tools":[{"name":...}]}, OpenAI [{"function":{"name":...}}], or {"functions":[{"name":...}]}.
  3. If tools legitimately use a different name key, normalize it before piping: jq 'map(.toolName as $n | .name = $n)'.
  4. Handle empty catalogs upstream — decide whether an empty tool list should skip the shrink entirely.

Example fix

// before: catalog with non-standard name key
{"tools":[{"toolName":"read_file","inputSchema":{...}}]}

// after: normalized before piping
// jq '.tools |= map(.name = (.name // .toolName))' catalog.json | caveman-shrink
{"tools":[{"name":"read_file","inputSchema":{...}}]}
Defensive patterns

Strategy: type-guard

Validate before calling

func hasNamedTools(b []byte) bool {
    var top any
    if json.Unmarshal(b, &top) != nil {
        return false
    }
    var list []any
    switch t := top.(type) {
    case []any:
        list = t
    case map[string]any:
        if v, ok := t["tools"].([]any); ok {
            list = v
        } else if v, ok := t["functions"].([]any); ok {
            list = v
        } else {
            list = []any{t}
        }
    }
    for _, item := range list {
        obj, ok := item.(map[string]any)
        if !ok {
            continue
        }
        if fn, ok := obj["function"].(map[string]any); ok {
            obj = fn
        }
        if n, _ := obj["name"].(string); n != "" {
            return true
        }
    }
    return false
}

Try / catch

entries, err := extractTools(input)
if err != nil {
    if strings.Contains(err.Error(), "no named tools found") {
        // catalog parsed but had zero tools: skip shrink or alert on the producer
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/13ec1893e15c40c0. Report an issue: GitHub.