You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of epic #8286 (Phase 3 — AI observability). Sibling of #10207/#10211, found while verifying those.
Problem
The ORB records exactly ~2 input tokens for every claude-code call, forever. Measured on edge-nl-01 over 48h:
provider
calls
input total
input avg
output total
output avg
cost
claude-code
1000
2048
2.0
1,051,346
1051.3
$225.06
An average of 2.0 across a thousand calls is not a measurement, it is a constant. The output side is healthy (1051/call) and the dollar cost is real, so the event is arriving and being parsed — the input figure specifically is meaningless.
Cause
mergeUsage (src/selfhost/ai.ts:821) reads the input side through:
Claude Code's stream-json result frame reports usage.input_tokens as only the uncached portion of the prompt. With prompt caching active — which it is for every review, since the system prompt and tool definitions are stable — essentially the entire prompt is served from cache, and the real volume lands in two keys this list does not contain:
cache_read_input_tokens
cache_creation_input_tokens
So input_tokens degenerates to the handful of genuinely-new tokens per call, which is the flat ~2 observed. Nothing is malformed and nothing is being dropped defensively — the extractor is reading a real field that simply does not mean what the name suggests under caching.
Impact
Cost-per-token and input:output ratio are wrong for the provider carrying the majority of ORB spend ($225 of $250 in the window).
loopover_ai_input_tokens_total (src/selfhost/ai.ts:876) has the same defect, so the Prometheus/Grafana view is wrong in the same direction.
Anything downstream that reasons about prompt size from this figure — budget backstops, neuron estimates — is reasoning from ~2.
Suggested fix
Add the two cache keys to the input-token accounting. They are genuine input tokens: the model processed them, and they are billed (at a reduced rate for cache reads).
Note this is not a matter of appending them to INPUT_TOKEN_KEYS. maxNumber takes the maximum across the key list, which is correct for aliases of one value (input_tokens vs prompt_tokens are two names for the same number) but wrong for these — the three are additive components of one prompt, so they must be summed and then combined with the alias-max result. Getting that distinction wrong would silently under-report again, just less severely.
Worth deciding explicitly whether to keep the cached portion separately as well, since PostHog has a recognised $ai_cache_read_input_tokens property and reporting it separately preserves the cache-effectiveness signal instead of flattening it into one number.
Deliverables
cache_read_input_tokens / cache_creation_input_tokens are counted as input tokens, summed with the uncached portion rather than max'd against it.
A regression test built from a real claude-code result frame carrying all three, pinning the summed total — and one with only input_tokens present, pinning that the un-cached path is unchanged.
loopover_ai_input_tokens_total reports the same corrected figure as the PostHog event, verified against the same fixture.
Decide whether the cache-read portion is also reported separately ($ai_cache_read_input_tokens).
Part of epic #8286 (Phase 3 — AI observability). Sibling of #10207/#10211, found while verifying those.
Problem
The ORB records exactly ~2 input tokens for every
claude-codecall, forever. Measured on edge-nl-01 over 48h:claude-codeAn average of 2.0 across a thousand calls is not a measurement, it is a constant. The output side is healthy (1051/call) and the dollar cost is real, so the event is arriving and being parsed — the input figure specifically is meaningless.
Cause
mergeUsage(src/selfhost/ai.ts:821) reads the input side through:Claude Code's
stream-jsonresult frame reportsusage.input_tokensas only the uncached portion of the prompt. With prompt caching active — which it is for every review, since the system prompt and tool definitions are stable — essentially the entire prompt is served from cache, and the real volume lands in two keys this list does not contain:cache_read_input_tokenscache_creation_input_tokensSo
input_tokensdegenerates to the handful of genuinely-new tokens per call, which is the flat ~2 observed. Nothing is malformed and nothing is being dropped defensively — the extractor is reading a real field that simply does not mean what the name suggests under caching.Impact
loopover_ai_input_tokens_total(src/selfhost/ai.ts:876) has the same defect, so the Prometheus/Grafana view is wrong in the same direction.Suggested fix
Add the two cache keys to the input-token accounting. They are genuine input tokens: the model processed them, and they are billed (at a reduced rate for cache reads).
Note this is not a matter of appending them to
INPUT_TOKEN_KEYS.maxNumbertakes the maximum across the key list, which is correct for aliases of one value (input_tokensvsprompt_tokensare two names for the same number) but wrong for these — the three are additive components of one prompt, so they must be summed and then combined with the alias-max result. Getting that distinction wrong would silently under-report again, just less severely.Worth deciding explicitly whether to keep the cached portion separately as well, since PostHog has a recognised
$ai_cache_read_input_tokensproperty and reporting it separately preserves the cache-effectiveness signal instead of flattening it into one number.Deliverables
cache_read_input_tokens/cache_creation_input_tokensare counted as input tokens, summed with the uncached portion rather than max'd against it.claude-coderesult frame carrying all three, pinning the summed total — and one with onlyinput_tokenspresent, pinning that the un-cached path is unchanged.loopover_ai_input_tokens_totalreports the same corrected figure as the PostHog event, verified against the same fixture.$ai_cache_read_input_tokens).