Skip to main content
Applies to:
  • Plan -
  • Deployment -

Summary

Symptom: a single 16-byte trace ID appears as two trace trees in the UI.
Cause: two emitters send the same bytes encoded as different strings (hex vs dashed UUID). Braintrust groups traces by exact root_span_id string equality.
Recommended fix: canonicalize root_span_id at the emitter(s) so all components send the same string form.

What is happening

Braintrust treats root_span_id as an opaque string. There is no server-side byte-level canonicalization.
If one emitter sends 00112233445566778899aabbccddeeff and another sends 00112233-4455-6677-8899-aabbccddeeff the platform sees two different root_span_id values.
Both values represent the same 16 bytes, but they are different strings, so traces split into separate trees.

Fix or suggestion

Pick one canonical string form (common choices below) and enforce it in every emitter or plugin.
  • Choice A — dashed UUID (example): 00112233-4455-6677-8899-aabbccddeeff
  • Choice B — 32-char hex (no dashes, common OTel span format): 00112233445566778899aabbccddeeff
Actionable steps:
  1. Choose the canonical form for your service boundary.
  2. Update each emitter/plugin to convert incoming root/span id bytes or strings to that form before emitting.
  3. Add a unit test or runtime assertion to fail if an emitter would emit a non-canonical form.
  4. Roll out changes and monitor for split traces.
Minimal conversion examples Python (bytes -> dashed UUID or no-dash hex)
import uuid

raw = b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
dashed = str(uuid.UUID(bytes=raw))   # "00112233-4455-6677-8899-aabbccddeeff"
nodash = uuid.UUID(bytes=raw).hex    # "00112233445566778899aabbccddeeff"
Bash (32-hex <-> dashed)
hex="00112233445566778899aabbccddeeff"
dashed=$(echo "$hex" | sed -E 's/^(.{8})(.{4})(.{4})(.{4})(.*)$/\1-\2-\3-\4-\5/')
# dashed -> hex
echo "$dashed" | tr -d '-'

Option 2: normalize in a shared plugin or preprocessor

If one component is widely reused, normalize there so callers do not need changes. Actionable steps:
  1. Update the shared plugin to accept incoming bytes/hex and emit the chosen canonical string.
  2. Add backward-compatible parsing for both forms (strip dashes or parse UUID) when reading inbound ids.
  3. Deploy the plugin carefully, and run tests across services that use it to check for compatibility/regressions.

How to confirm it worked

  • Log-based check: for a known trace_id, confirm there is exactly one root_span_id string. Example:
    jq -r 'select(.trace_id=="<TRACE_ID>") | .root_span_id' traces.json | sort | uniq -c
    
    Expect a single unique value.
  • UI check: the trace with that trace_id appears as one tree (no duplicate root spans).

Notes

  • Braintrust groups traces by exact root_span_id string equality; choose and enforce one encoding across emitters.
  • Common practice: use the format already dominant in your environment (OTel default is 32-char hex for span IDs; some tools prefer dashed UUIDs).