{"record":{"id":"1fa3d40a5cdfef9a","repo":"JuliusBrussee/caveman","slug":"cave-subagent-spend-evidence-incomplete","errorCode":"cave_subagent_spend_evidence_incomplete","errorMessage":"cave_subagent_spend_evidence_incomplete","messagePattern":"cave_subagent_spend_evidence_incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":2101,"sourceCode":"        yield { type: \"pi\", runId, event: queued.shift()! };\n      }\n    }\n    await execution;\n    if (efficiencyPlan && reasoningUsageUnavailable) {\n      throw new Error(\"cave_reasoning_usage_unavailable\");\n    }\n    if (ladderFailure) throw ladderFailure;\n    if (spendFailure) throw spendFailure;\n    if (usageFailure !== undefined &&\n        (efficiencyPlan !== undefined ||\n          executionContext.spendLedgers.length > 0 ||\n          usageFailure.message === \"cave_provider_model_identity_mismatch\" ||\n          usageFailure.message === \"cave_provider_identity_missing\")) {\n      throw usageFailure;\n    }\n    if (pendingSpendReservations.length > 0) {\n      markSpendIncomplete(executionContext.spendLedgers);\n      throw new Error(\"cave_subagent_spend_evidence_incomplete\");\n    }\n    // A run stopped before its first call has no assistant message, and that is\n    // the honest outcome rather than missing evidence: the caller gets an empty\n    // answer, zero usage, and the reason the runtime declined to spend.\n    if (!finalMessage && stopReason === undefined) {\n      throw new Error(\"cave_incomplete_evidence: Pi emitted no final assistant message\");\n    }\n    if (finalMessage &&\n        (finalMessage.stopReason === \"error\" || finalMessage.stopReason === \"aborted\")) {\n      throw new Error(`cave_provider_terminal_${finalMessage.stopReason}`);\n    }\n    const text = finalMessage === undefined ? \"\" : assistantText(finalMessage);\n    if (definition.output?.schema && finalMessage !== undefined) {\n      let parsed: unknown;\n      try {\n        parsed = JSON.parse(text);\n      } catch {\n        throw new Error(\"cave_output_schema_invalid_json\");","sourceCodeStart":2083,"sourceCodeEnd":2119,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L2083-L2119","documentation":"Thrown when the run ends while pendingSpendReservations is non-empty: a spend reservation was taken for a provider call but never settled with usage evidence. The ledgers are marked incomplete first, so spend accounting fails closed rather than silently under-reporting.","triggerScenarios":"A provider call is reserved and then the run terminates (error, abort, exception in the stream) before the reservation is spent or released — e.g. streamFn throws mid-call, an AbortSignal fires between reservation and settlement, or a nested subagent run dies with unsettled reservations.","commonSituations":"AbortSignal triggered mid-request; provider connection drop after reservation; a bug in a custom streamFn that throws after the runtime reserved budget; nested subagent failures bubbling up.","solutions":["Identify why the run terminated between reservation and settlement: check ladderFailure/spendFailure/usageFailure which are thrown earlier and are the root cause","If aborting mid-call is intentional in your app, catch this error and treat the run as incomplete (the ledger is already marked incomplete for you)","Retry the run — reservations are per-run, so a fresh run re-reserves cleanly","Audit custom streamFn wrappers to ensure they don't swallow usage events after the call starts"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isSpendEvidenceIncomplete(e: unknown): e is Error {\n  return e instanceof Error && e.message === \"cave_subagent_spend_evidence_incomplete\";\n}","tryCatchPattern":"try {\n  await agent.run(input, opts);\n} catch (e) {\n  if (isSpendEvidenceIncomplete(e)) {\n    // ledger already marked incomplete; treat run as failed/incomplete and retry fresh\n  } else throw e;\n}","preventionTips":["Don't abort runs mid-call unless you accept incomplete spend accounting","Fix earlier failures (ladder/spend/usage) that terminate runs between reservation and settlement","Treat this error as a symptom — always log the preceding failure too"],"tags":["spend","reservations","accounting","abort"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}