JuliusBrussee/caveman · error · Error
cave_vercel_terminal_failure
cave_vercel_terminal_failure
Error message
cave_vercel_terminal_failure
What it means
The Vercel AI SDK adapter throws `cave_vercel_terminal_failure` after `agent.generate` returns when `response.error !== undefined` or `terminalFinishReason(response.finishReason) === false`. `terminalFinishReason` accepts exactly `"stop"`, `"length"`, `"content-filter"`, and `"other"` — anything else (`undefined`, `"tool-calls"`, `"error"`, `"unknown"`, custom strings) is non-terminal. In practice this means the ToolLoopAgent ended without a terminal model stop (e.g. its step budget ran out while tools still wanted to run) or the response carried an explicit error object.
Solutions
- Reproduce with a raw `agent.generate` call and inspect `response.error` and `response.finishReason` to classify the failure
- If finishReason is `tool-calls`, raise the agent's step/stopWhen budget so the loop reaches a model stop
- If `response.error` is set, fix credentials/rate limits before retrying — this code is not transient by itself
- Pin `ai` at exactly 7.0.43 (the adapter's `assertSupportedUpstream` enforces this installed version)
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: prove the agent reaches a terminal stop and reports v4 usage
// with one cheap raw call before wiring the adapter
const probe = await agent.generate({ prompt: 'ping' });
if (probe.error !== undefined) throw new Error(`agent misconfigured: ${String(probe.error)}`);
if (!['stop', 'length', 'content-filter', 'other'].includes(String(probe.finishReason))) {
throw new Error(`agent does not terminate: finishReason=${String(probe.finishReason)}`);
} Try / catch
try {
return await adapter.run(req);
} catch (e) {
if (e instanceof Error && e.message === 'cave_vercel_terminal_failure') {
// classify with one raw generate: response.error → provider/config issue;
// finishReason 'tool-calls' → raise the agent's step/stopWhen budget
const diag = await agent.generate({ prompt: 'ping' });
log.diag({ error: diag.error, finishReason: diag.finishReason });
}
throw e;
} Prevention
- Build the ToolLoopAgent with a step/stopWhen budget large enough to always reach a model stop
- Validate provider credentials with a cheap call before locked runs
- Pin ai@7.0.43 exactly — the adapter enforces that installed version anyway
When it happens
Trigger: A ToolLoopAgent whose `stopWhen` budget is exhausted so `finishReason` stays `"tool-calls"`; the SDK setting `response.error` on provider auth/rate-limit failures; a signal aborted mid-loop producing an error or undefined finishReason.
Common situations: Misconfigured agent step budget / stopWhen that never triggers; invalid or expired provider API key; provider outage surfacing as an error field; runaway tool loops burning all steps before a final model answer.
Related errors
- cave_mastra_terminal_failure
- cave_vercel_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/5be1f7fe47be181a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/adapters.ts:213
export function createVercelAISDKAdapter(
identity: HarnessAdapterIdentity,
agent: VercelToolLoopAgentBinding,
): HarnessAdapter {
assertSupportedUpstream(identity, VERCEL_AI_SDK_VERSION, "vercel", "ai");
return createHarnessAdapter("vercel-ai-sdk", identity, {
package: "ai",
class: "ToolLoopAgent",
method: "generate",
usage: "LanguageModelUsage.v4",
}, async (request) => {
const startedAt = performance.now();
const response = await agent.generate({
prompt: request.prompt,
...(request.signal === undefined ? {} : { abortSignal: request.signal }),
});
if (response.error !== undefined || terminalFinishReason(response.finishReason) === false) {
throw new Error("cave_vercel_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: usageFromAISDK(response.usage, request.plan.reasoning !== "none"),
latencyMs: Math.round(performance.now() - startedAt),
});
});
}
export interface EveMessageResponseBinding {
result(): Promise<{
message: string | undefined;
status: "completed" | "failed" | "waiting";View on GitHub (pinned to 766dce6b13)