Skip to content

Commit 31ca370

Browse files
RunnanJiaclaude
andcommitted
fix(platform): emit W3C-compliant traceparent so the gateway accepts it
build_trace_context_headers emitted x-uipath-traceparent-id as "00-{trace}-{span}" with a 32-hex span id and no trace-flags segment. The LLM Gateway parses this header strictly (UiPath.Tracing.TraceParent.TryParse): it requires exactly four segments {version}-{32-hex trace}-{16-hex span}-{flags} and a 16-hex span id. The malformed header was rejected, so the gateway synthesized a fresh root trace for every LLM call and dropped the inbound baggage — the gateway's completion (audit) span ended up under a different traceId with a null parent, orphaned from the agent run, and the jobKey/source baggage this PR adds never reached the gateway. Fix: format the span id as 16 hex (`016x`, a span id is 64-bit) and append the `-01` trace-flags segment. Verified safe for the other consumer (the Agents backend parser is lenient: >=3 parts, no span-length check). Updated the test to assert the correct 4-segment / 16-hex shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bfe2c1a commit 31ca370

2 files changed

Lines changed: 15 additions & 6 deletions

File tree

packages/uipath-platform/src/uipath/platform/chat/llm_trace_context.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,14 @@ def build_trace_context_headers(
3434
ctx = span.get_span_context()
3535
if config_trace_id and ctx and ctx.span_id:
3636
trace_id = _SpanUtils.normalize_trace_id(config_trace_id)
37-
span_id = format(ctx.span_id, "032x")
38-
headers["x-uipath-traceparent-id"] = f"00-{trace_id}-{span_id}"
37+
# An OTEL span id is 64-bit => 16 lowercase hex chars, and the W3C traceparent
38+
# requires the trailing trace-flags segment. The LLM Gateway's strict parser
39+
# (UiPath.Tracing.TraceParent.TryParse) rejects the header unless it is exactly
40+
# {version}-{32-hex trace}-{16-hex span}-{2-hex flags}; on rejection the gateway
41+
# synthesizes a fresh root trace and drops inbound baggage, orphaning the audit
42+
# span from the caller's trace. Emitting 32-hex span / no flags was the bug.
43+
span_id = format(ctx.span_id, "016x")
44+
headers["x-uipath-traceparent-id"] = f"00-{trace_id}-{span_id}-01"
3945

4046
baggage_parts: list[str] = list(extra_baggage) if extra_baggage else []
4147
if folder_key := UiPathConfig.folder_key:

packages/uipath-platform/tests/services/test_llm_trace_context.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def setup_method(self) -> None:
4444
def test_traceparent_from_config_and_span(self) -> None:
4545
span = _make_span()
4646
ctx = span.get_span_context()
47-
expected_span_id = format(ctx.span_id, "032x")
47+
# OTEL span id is 64-bit => 16 hex chars; traceparent carries the trailing flags
48+
# segment. The gateway's strict W3C parser requires exactly this shape.
49+
expected_span_id = format(ctx.span_id, "016x")
4850
config_trace = "abcdef1234567890abcdef1234567890"
4951
env = {"UIPATH_TRACE_ID": config_trace}
5052
with (
@@ -58,12 +60,13 @@ def test_traceparent_from_config_and_span(self) -> None:
5860

5961
assert "x-uipath-traceparent-id" in headers
6062
value = headers["x-uipath-traceparent-id"]
61-
assert value == f"00-{config_trace}-{expected_span_id}"
63+
assert value == f"00-{config_trace}-{expected_span_id}-01"
6264
parts = value.split("-")
63-
assert len(parts) == 3
65+
assert len(parts) == 4
6466
assert parts[0] == "00"
6567
assert len(parts[1]) == 32
66-
assert len(parts[2]) == 32
68+
assert len(parts[2]) == 16
69+
assert parts[3] == "01"
6770

6871
def test_no_traceparent_without_config_trace_id(self) -> None:
6972
headers = build_trace_context_headers()

0 commit comments

Comments
 (0)