JuliusBrussee/caveman · error
cave_subagent_spend_evidence_incomplete
cave_subagent_spend_evidence_incomplete
Error message
cave_subagent_spend_evidence_incomplete
What it means
'cave_subagent_spend_evidence_incomplete' is thrown when the run ends while spend reservations are still pending (pendingSpendReservations.length > 0), meaning provider responses never arrived that could be settled against reserved spend (runtime.ts:2099-2101). The runtime marks the spend ledgers incomplete and fails closed so a subagent cannot report a result whose cost evidence is missing.
Solutions
- Check RunResult/receipt and provider connectivity for the call that died between reservation and settlement; fix the underlying provider/stream failure first.
- Retry the run; reservations are per-run, so a fresh run rebuilds its ledger.
- Remove the caller abort (options.signal) trigger if the abort fires between subagent spawn and first response.
- If reproducible, inspect markSpendIncomplete ledger output in the receipt to identify which call never settled.
Example fix
// before: aborting while a subagent call is still in flight leaves reservations unsettled
const run = streamAgent(agent, input, { signal: controller.signal });
setTimeout(() => controller.abort(), 1000);
// after: abort only after the stream settles, or use deadlineMs for bounded runs
const result = await streamAgent(agent, input, { deadlineMs: 30000 }); Defensive patterns
Strategy: retry
Try / catch
try {
await runAgent(agent, input);
} catch (e) {
if (e instanceof Error && e.message === "cave_subagent_spend_evidence_incomplete") {
// a subagent died between spend reservation and settlement — retry the run
await runAgent(agent, input);
}
} Prevention
- Don't abort the run signal between subagent spawn and first provider response.
- Use deadlineMs instead of external aborts so stops land between calls.
- Monitor provider stream stability; flaky connections leave reservations unsettled.
- Inspect receipt spend ledgers after failures to identify the unsettled call.
When it happens
Trigger: A subagent (or nested) run that reserved spend for provider calls but terminated before those calls' assistant messages were accounted — e.g. the provider stream errored or the run was aborted mid-call after reservation, leaving reservations unsettled when streamAgentInternal reaches its final checks.
Common situations: Provider connection drops mid-subagent-call; caller aborts the run signal after a nested spawn reserved its turn; a provider bug yields assistant messages that never flow through the accounting path; timeout kills a call between reservation and response.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- cave_claude_terminal_missing
- cave_subagent_cost_budget
- bedrock request path
- cave_claude_subagent_bridge_unavailable
- cave_fixture_terminal_evidence_missing
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/1fa3d40a5cdfef9a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:2101
yield { type: "pi", runId, event: queued.shift()! };
}
}
await execution;
if (efficiencyPlan && reasoningUsageUnavailable) {
throw new Error("cave_reasoning_usage_unavailable");
}
if (ladderFailure) throw ladderFailure;
if (spendFailure) throw spendFailure;
if (usageFailure !== undefined &&
(efficiencyPlan !== undefined ||
executionContext.spendLedgers.length > 0 ||
usageFailure.message === "cave_provider_model_identity_mismatch" ||
usageFailure.message === "cave_provider_identity_missing")) {
throw usageFailure;
}
if (pendingSpendReservations.length > 0) {
markSpendIncomplete(executionContext.spendLedgers);
throw new Error("cave_subagent_spend_evidence_incomplete");
}
// A run stopped before its first call has no assistant message, and that is
// the honest outcome rather than missing evidence: the caller gets an empty
// answer, zero usage, and the reason the runtime declined to spend.
if (!finalMessage && stopReason === undefined) {
throw new Error("cave_incomplete_evidence: Pi emitted no final assistant message");
}
if (finalMessage &&
(finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
throw new Error(`cave_provider_terminal_${finalMessage.stopReason}`);
}
const text = finalMessage === undefined ? "" : assistantText(finalMessage);
if (definition.output?.schema && finalMessage !== undefined) {
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
throw new Error("cave_output_schema_invalid_json");View on GitHub (pinned to 3ee70a1026)