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
{{ message }}
Repository navigation
[OTel] _flatten_nested_dict bypasses the shared decoder, so nested-format traces can still produce unhashable tags #207
OtlpJsonLoader._extract_attributes has two branches, and only one goes through the shared AnyValue decoder:
def_extract_attributes(self, attrs) ->dict:
ifisinstance(attrs, dict):
returnself._flatten_nested_dict(attrs) # no decoding, no allowlistreturndecode_attributes(attrs) # decoder + SPEC_CONTAINER_ATTRS
The dict branch handles the flat and nested (ClickHouse JSON column) formats.
It recurses into dicts and passes everything else through untouched:
forkey, valueind.items():
full_key=f"{prefix}{key}"ifnotprefixelsef"{prefix}.{key}"ifisinstance(value, dict):
result.update(OtlpJsonLoader._flatten_nested_dict(value, full_key))
else:
result[full_key] =value# a list lands in span.tags as a list
So the guarantee #187 establishes on the OTLP-array path — containers only for allowlisted keys — does not hold here. A list reaches span.tags, and gcp.vertex.agent.invocation_id is then used as a dict key in eight places in incremental_processor.py (llm_spans_by_invocation, token_totals, seen_tool_calls).
This predates #187 — the dict branch never had any narrowing — so it is not a regression from that PR, but it is the same hazard on a path the PR does not cover.
Expected behavior
Both branches of _extract_attributes should end up with the same contract: container values only for keys on the allowlist, narrowed otherwise. A nested-format trace should not be able to put an unhashable value where the OTLP-array format cannot.
Steps to reproduce
Load a nested-format (ClickHouse JSON column) trace whose attributes carry a list where a scalar is expected:
_flatten_nested_dict flattens it to {"gcp.vertex.agent.invocation_id": ["a", "b"]}, which reaches self.seen_tool_calls[invocation_id] in incremental_processor.py and raises TypeError: unhashable type: 'list'.
Activity
fix(otel): decode containers on an opt-in allowlist instead of a deny…
fix(otel): share one AnyValue decoder across all OTLP paths (#187)