SIGN IN SIGN UP

fix: re-parent exported spans whose parent was dropped (#14737)

* fix: re-parent exported spans whose parent was dropped

A span nested inside a dropped LLM span reached the APM pointing at a parent
that never arrived, so the trace rendered with a hole in it. It now attaches to
its nearest exported ancestor, or becomes a root when it has none.

The docstring said this could not be repaired here, because a child ends
before its parent and so cannot know the parent's fate. That reasoning holds
only if the fate is decided at the parent's end. It is not: the drop depends
only on the instrumentation scope, which is fixed when the span starts.
on_start fires for every span, dropped ones included, so recording scope and
parentage there answers the question at the child's on_end.

Only rewrites when the parent is known to have been dropped. An unknown parent
is left exactly as it is: that is a remote parent from another process, and
treating absence as a drop would detach every distributed trace from its caller
and re-root it locally, replacing a correct link with an invented one. That
case has its own test, and the test fails if the sentinel distinguishing
"unknown" from "no exported ancestor" is removed.

The map is bounded and entries are removed as spans end, since it is the only
state this processor keeps and a leak here would be a leak in a long-running
server. Also covered by a test that fails with 75 entries left behind when the
cleanup is dropped.

Replaces the test that pinned the orphaning as a known consequence.

* fix: never let re-parenting raise into the run that ended the span

Re-parenting writes span._parent, which is SDK internals, and the SDK does not
catch exceptions from a span processor: it lets them out of Span.end() and into
whatever application code ended the span. Verified directly, with a processor
that raises in on_end.

So an OpenTelemetry release that renames that attribute would not merely stop
traces rendering as trees, it would raise inside a flow run. Telemetry is not
allowed to break the thing it observes. Span.__slots__ is empty, so a renamed
field raises AttributeError rather than silently writing a dead attribute.

This is the same risk the repo already guards for _get_route_details in
observability_fastapi, which checks the attribute is there and leaves a
breadcrumb when it is not. Failures are logged once and the span still exports
with the parent it already had.

The first version of the test for this passed with the guard removed: the
corrupted entry it used was a string, so entry[0] was truthy and the code
returned before reaching anything that could raise. It now uses a short tuple,
which is falsy in position 0 and raises in the walk. Without the guard it fails
with ValueError: not enough values to unpack.

* fix: preserve remote parent across dropped spans

---------

Co-authored-by: Eric Hare <ericrhare@gmail.com>
G
Gabriel Luiz Freitas Almeida committed
358a2b8b1b61c30f2e76aa57454eedd6b836cfd9
Parent: c813fa9
Committed by GitHub <noreply@github.com> on 8/24/2026, 6:50:16 PM