JuliusBrussee/caveman · error · Error
cave_mastra_terminal_failure
cave_mastra_terminal_failure
Error message
cave_mastra_terminal_failure
What it means
Mirrors the Vercel gate: after `agent.generate` returns, `cave_mastra_terminal_failure` throws when `response.error !== undefined` or `finishReason` is outside `"stop" | "length" | "content-filter" | "other"`. The most characteristic trigger is Mastra's `maxSteps` being exhausted while tools still want to run — the tool loop ends without a terminal model stop — with provider errors surfaced via `response.error` as the other main cause.
Solutions
- Raise the adapter's `options.maxSteps` above the expected tool-loop depth and re-run
- Run a raw `agent.generate` to inspect `response.error`/`finishReason` and separate provider errors from step exhaustion
- Simplify or batch the tool chain so the model reaches a stop within the cap
- Pin `@mastra/core` at exactly 1.55.0 — `assertSupportedUpstream` requires that installed version anyway
Example fix
// before
createMastraAdapter(identity, agent, { maxSteps: 5 }); // tool chain needs ~12 calls
// after
createMastraAdapter(identity, agent, { maxSteps: 25 }); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: confirm the workflow terminates inside the step budget
// by watching finishReason on a raw generate once
const probe = await agent.generate(prompt, { maxSteps, maxProcessorRetries: 0 });
if (probe.error !== undefined ||
!['stop', 'length', 'content-filter', 'other'].includes(String(probe.finishReason))) {
throw new Error(`workflow exceeds maxSteps=${maxSteps}; finishReason=${String(probe.finishReason)}`);
} Try / catch
try {
return await adapter.run(req);
} catch (e) {
if (e instanceof Error && e.message === 'cave_mastra_terminal_failure') {
// 'tool-calls' finish → raise options.maxSteps above the tool-loop depth;
// response.error → provider/credential issue; inspect with one raw generate
}
throw e;
} Prevention
- Budget maxSteps above the workflow's worst-case sequential tool depth, with headroom
- Watch finishReason in dev runs as you add tools — growth crosses the cap silently
- Pin @mastra/core at 1.55.0, the only version the adapter accepts
When it happens
Trigger: A tool-calling workflow whose depth exceeds the adapter's `maxSteps` so the loop ends non-terminally; provider auth/rate-limit failures populating `response.error`; an aborted generation returning an error or undefined finishReason.
Common situations: `maxSteps` tuned below the workflow's real tool-loop depth; flaky provider credentials; long sequential tool chains added over time until they cross the cap.
Related errors
- cave_vercel_terminal_failure
- cave_mastra_usage_missing
- cave_eve_terminal_${result.status}
- cave_eve_usage_missing
- cave_harness_incomplete_evidence
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/4359d6b23c566c1c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/adapters.ts:320
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");
}
const expected = expectedProviderModel(request.plan);
const actualModel = normalizedResponseModel(response.response?.modelId, expected.provider);
return harnessExecution({
request,
text: response.text,
provider: expected.provider,
model: actualModel,
usage: usageFromMastra(response.totalUsage ?? response.usage, request.plan.reasoning !== "none"),
latencyMs: Math.round(performance.now() - startedAt),
});
});
}
interface NormalizedUsage {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;View on GitHub (pinned to 766dce6b13)