Skip to content

ai(observability): claude-code input tokens under-report by orders of magnitude — cache tokens are never read #10246

Description

@JSONbored

Part of epic #8286 (Phase 3 — AI observability). Surfaced by #10212.

Problem

Token counts now reach PostHog (#10212 fixed the scrubber that was nulling them), and the input side is badly under-reported for the claude-code provider. Real events captured minutes after deploying orb-v3.7.0-beta.9:

model provider $ai_input_tokens $ai_output_tokens $ai_total_cost_usd
claude-sonnet-5 claude-code 2 787 $0.2145
claude-sonnet-5 claude-code 2 157 $0.0801
qwen3:8b ollama 2706 544 $0.000564

Two input tokens for a review that produced 787 output tokens and cost $0.21 is not plausible — a review prompt carries the diff plus assembled repo context. The local ollama model on the same box reports 2,706, which is the realistic shape.

Cause

extractCliUsage reads only these keys (src/selfhost/ai.ts):

const INPUT_TOKEN_KEYS = ["input_tokens", "inputTokens", "prompt_tokens", "promptTokens"] as const;

There is no reference anywhere in the file to cache_read_input_tokens or cache_creation_input_tokens.

Anthropic's usage envelope reports cached prompt tokens separately from input_tokens. On a cache hit — which is the normal case for this engine, since the system prompt and repo context are stable across a review — input_tokens contains only the small uncached remainder, and the bulk sits in cache_read_input_tokens. So the parser faithfully records the one number the CLI put in the field it looks at, and silently drops the rest.

This was invisible until now: the redaction bug was nulling every token count, so there was nothing to notice.

Impact

  • $ai_input_tokens under-reports claude-code input by orders of magnitude.
  • PostHog derives $ai_input_cost_usd / $ai_output_cost_usd from token counts, so the derived input cost is ~0 and the input/output cost split is meaningless. ($ai_total_cost_usd is unaffected — it is sent directly from the CLI's own total_cost_usd.)
  • Cost-per-token and cache-hit-rate analysis are impossible.
  • The same parser backs loopover_ai_input_tokens_total in Prometheus, so Grafana under-reports identically.

PostHog already models this

posthog.ai_events has native cache_read_input_tokens and cache_creation_input_tokens columns, and $ai_generation carries a $ai_cache_reporting_exclusive boolean documented as: "Whether cache tokens are excluded from the input token count. When true, cache tokens are separate from input tokens (Anthropic-style)." So the vendor contract has a first-class slot for exactly this — we simply never populate it.

Deliverables

  • Parse cache_read_input_tokens and cache_creation_input_tokens from the CLI usage envelope.
  • Carry them through AiUsage and emit them as their own PostHog properties rather than silently folding them into $ai_input_tokens — the split is real information (cache hit rate), and flattening it would trade one inaccuracy for another.
  • Set $ai_cache_reporting_exclusive so PostHog knows the provider's convention and computes cost correctly.
  • Decide deliberately whether $ai_input_tokens stays the uncached remainder (matching Anthropic's own semantics, with cache reported alongside) or becomes the true total. Whichever is chosen, document it where INPUT_TOKEN_KEYS is defined — the current behaviour is not a decision, it is an omission.
  • Regression test with a realistic claude-code usage envelope: small input_tokens, large cache_read_input_tokens.
  • Check the miner's extractCliUsage equivalent (packages/loopover-engine/src/miner/cli-subprocess-driver.ts) for the same gap — fix(observability): report the miner's real input/output token split to PostHog #10199/feat(observability): capture every miner AI generation, and stop fabricating zero token counts #10233 landed the input/output split there, but it reads the same key families.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions