Files
zzgandClaude Opus 5 4d0bedd85a Read insight attributes as aliases, not as one emitter's vocabulary
Fathom ingests OTLP from anything, and ingest already knew emitters
disagree about names: classify() keys on gen_ai.operation.name as well
as Claude Code's span.type, and the correlator's join keys list
gen_ai.tool.call.id alongside tool_use_id.

The insights analyzer did not. It read cost_usd, input_tokens,
output_tokens, cache_read_tokens, model, stop_reason, tool_name and
user_prompt -- Claude Code's private vocabulary, exclusively. A trace
from any other emitter therefore rendered correctly in the trace view
and reported no cost, no tokens, and every tool named "tool". That is a
silent wrong answer, and the kind that presents as "you have no
findings" rather than as a bug.

Each fact now resolves through an ordered list of names with Claude
Code's own first, so a trace carrying both vocabularies resolves to the
name the analyzer was written against and no existing answer changes.
Resolution is first-match-wins rather than sum-across-matches, so an
emitter stamping both names on one span does not get its tokens counted
twice -- there is a test pinning that.

Cost gets one extra wrinkle: Claude Code reports it on the log records,
a GenAI emitter reports it on the span. costOf() checks the span first
and short-circuits, so a trace carrying both does not pay twice.

This is a read-time alias, not an ingest-time rewrite. What an emitter
sent stays on disk verbatim, the same reason classify() keeps the
original span name whatever kind it decides on.

Verified against a real store, not only in unit tests: a pi run emitted
by costslayer's new OTLP layer previously extracted to $0.00000 and
zero tokens, and now extracts to $0.04218, 12,560 tokens, 11,000
cached, both tool names, and the correct model and stop reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:31:53 -07:00
..