{"record":{"id":"bab5282bd1fa5e44","repo":"JuliusBrussee/caveman","slug":"cave-claude-terminal-missing","errorCode":"cave_claude_terminal_missing","errorMessage":"the SDK reported no accountable usage on a successful result","messagePattern":"the SDK reported no accountable usage on a successful result","errorType":"error_code","errorClass":"CavemanRunError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":351,"sourceCode":"      );\n    }\n    const text = result.result;\n    if (definition.output?.schema !== undefined) {\n      let parsed: unknown;\n      try {\n        parsed = result.structured_output ?? JSON.parse(text);\n      } catch {\n        throw carry(\"cave_output_schema_invalid_json\", \"the SDK output was not valid JSON\");\n      }\n      if (!Value.Check(definition.output.schema, parsed)) {\n        throw carry(\"cave_output_schema_mismatch\", \"the SDK output did not match the declared schema\");\n      }\n    }\n    // Past the success gate, usage must be accountable. A success whose usage\n    // validateProviderUsage rejected is a real evidence failure — surface it\n    // carrying the receipt, never mask it.\n    if (usage === undefined) {\n      throw carry(\n        usageError?.message ?? \"cave_claude_terminal_missing\",\n        \"the SDK reported no accountable usage on a successful result\",\n      );\n    }\n    if (definition.output !== undefined && usage.outputTokens > definition.output.maxTokens) {\n      // The SDK already ran and spent by the time its aggregate usage is known,\n      // so this post-hoc budget breach carries the receipt of what was spent\n      // rather than throwing it away.\n      throw carry(\n        \"cave_output_budget_exceeded\",\n        `output ${usage.outputTokens} exceeded budget ${definition.output.maxTokens}`,\n      );\n    }\n    return {\n      runId: runID,\n      agentId: definition.id,\n      text,\n      contextIR: lowered.ir,","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/claude-runtime.ts#L333-L369","documentation":"On a successful SDK result the executor must produce accountable usage (token counts that pass `usageFromSDKResult`/`validateProviderUsage`). If usage extraction failed (recorded as `usageError`) or was absent, a success cannot be evidenced, so the run throws — using the underlying usage-validation error's message as the code when available, else `cave_claude_terminal_missing`. The library fails closed because every past-the-gate success must carry a provider-reported usage receipt; an unevidenced success is treated as a terminal failure.","triggerScenarios":"The SDK returned `subtype: \"success\"` but `result.usage` is missing/zero/incomplete so `usageFromSDKResult` threw (e.g. `cave_provider_usage_incomplete`); a proxy or gateway stripped usage fields; an SDK version that omits aggregate usage on the result message.","commonSituations":"Running behind custom gateways/proxies that drop the `usage` object; mocking or stubbing SDK responses in tests without usage; exotic auth regimes (OAuth subscription flows) where the SDK reports no per-run usage; SDK upgrades changing the result shape.","solutions":["Read the thrown code (`usageError.message`, e.g. `cave_provider_usage_incomplete`) to see which usage field was missing, then fix the transport that drops it.","Remove intermediaries (custom proxy, request rewriter) or re-add the `usage` block to responses.","Pin/restore the exact-pinned Claude Agent SDK version whose result message carries aggregate usage.","If this happens in tests, make SDK fakes emit realistic `usage` with input/output tokens."],"exampleFix":"// before (test fake)\nconst fakeResult = { subtype: \"success\", result: \"ok\" };\n// after\nconst fakeResult = { subtype: \"success\", is_error: false, result: \"ok\", usage: { input_tokens: 100, output_tokens: 20 } };","handlingStrategy":"try-catch","validationCode":"// Ensure no proxy strips usage: assert a probe response carries the usage block.\nconst probe = await fetch(baseUrl + \"/v1/messages\", { method: \"POST\", /* minimal request */ });\nconst body = await probe.json();\nif (!body.usage) throw new Error(\"transport strips usage — accountable runs will fail\");","typeGuard":"function hasUsage(r: unknown): r is { usage: { input_tokens: number; output_tokens: number } } {\n  return typeof r === \"object\" && r !== null && \"usage\" in r &&\n    typeof (r as any).usage?.input_tokens === \"number\";\n}","tryCatchPattern":"try {\n  const r = await runClaudeAgent(opts);\n} catch (e) {\n  if (e instanceof CavemanRunError && e.code.startsWith(\"cave_provider_usage\")) {\n    // the code names the exact missing usage field; fix the transport or SDK pin\n  }\n  throw e;\n}","preventionTips":["Never put a response-rewriting proxy in front of the Anthropic API for evidenced runs.","Keep the Claude Agent SDK at the exact-pinned version.","Make SDK fakes/mocks emit full usage objects in tests.","Run `caveman doctor` to surface transport/route issues before spending."],"tags":["usage","evidence","claude-agent-sdk","fail-closed"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}