From 5d91dcf127a50652a92f940640c41373b1941eb6 Mon Sep 17 00:00:00 2001 From: "Gareth.Y" <91742523+linjiaye929@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:03:16 -0700 Subject: [PATCH] fix: preserve custom ID case in exported span parents --- py/src/braintrust/span_identifier_v4.py | 6 ++-- py/src/braintrust/test_span_components.py | 34 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/py/src/braintrust/span_identifier_v4.py b/py/src/braintrust/span_identifier_v4.py index a8a67ff99..c0e5f75ac 100644 --- a/py/src/braintrust/span_identifier_v4.py +++ b/py/src/braintrust/span_identifier_v4.py @@ -21,7 +21,8 @@ def _try_make_hex_trace_id(s): if isinstance(s, str) and len(s) == 32: # 32 hex chars = 16 bytes ret = bytes.fromhex(s) assert len(ret) == 16 - return ret, True + if ret.hex() == s: + return ret, True except Exception: pass return None, False @@ -33,7 +34,8 @@ def _try_make_hex_span_id(s): if isinstance(s, str) and len(s) == 16: # 16 hex chars = 8 bytes ret = bytes.fromhex(s) assert len(ret) == 8 - return ret, True + if ret.hex() == s: + return ret, True except Exception: pass return None, False diff --git a/py/src/braintrust/test_span_components.py b/py/src/braintrust/test_span_components.py index fa12a94e1..cb83057c6 100644 --- a/py/src/braintrust/test_span_components.py +++ b/py/src/braintrust/test_span_components.py @@ -76,6 +76,40 @@ def test_otel_ids_fail_roundtrip(self): class TestSpanComponentsV4: """Test SpanComponentsV4 functionality and OTEL compatibility.""" + @pytest.mark.parametrize( + ("span_id", "root_span_id"), + [ + ("ABCDEF1234567890", "abcdef0123456789abcdef0123456789"), + ("abcdef1234567890", "ABCDEF0123456789ABCDEF0123456789"), + ("AbCdEf1234567890", "AbCdEf0123456789AbCdEf0123456789"), + ("abcdef1234567890", "abcdef0123456789abcdef0123456789"), + ("0123456789012345", "01234567890123456789012345678901"), + ("custom-span", "custom-root"), + ("72EAC0DD-0775-4D40-BAC8-1DFC8CC14059", "72EAC0DD-0775-4D40-BAC8-1DFC8CC14059"), + ], + ) + def test_exported_custom_ids_preserve_parent_link(self, span_id, root_span_id, with_memory_logger): + from braintrust.test_helpers import init_test_logger, preserve_env_vars + + with preserve_env_vars("BRAINTRUST_LEGACY_IDS"): + os.environ.pop("BRAINTRUST_LEGACY_IDS", None) + log = init_test_logger("custom-id-roundtrip") + with log.start_span(name="parent", span_id=span_id, root_span_id=root_span_id) as parent: + exported = parent.export() + with log.start_span(name="child", parent=exported): + pass + + rows = with_memory_logger.pop() + parent_row = next(row for row in rows if row["span_attributes"]["name"] == "parent") + child_row = next(row for row in rows if row["span_attributes"]["name"] == "child") + assert parent_row["span_id"] == span_id + assert parent_row["root_span_id"] == root_span_id + assert child_row["span_parents"] == [parent_row["span_id"]] + assert child_row["root_span_id"] == parent_row["root_span_id"] + decoded = SpanComponentsV4.from_str(exported) + assert decoded.span_id == span_id + assert decoded.root_span_id == root_span_id + def test_otel_hex_strings_preserved(self): """Test that V4 preserves OTEL hex strings exactly.""" otel_gen = OTELIDGenerator()