@@ -691,13 +691,14 @@ def test_deep_nesting(self):
691691 assert uuids .index (root ) < uuids .index (mid ) < uuids .index (leaf )
692692
693693 def test_parent_outside_batch (self ):
694- """Spans referencing a parent not in the batch are treated as roots ."""
694+ """Spans referencing a missing parent are detached before insertion ."""
695695 external_parent = uuid4 ()
696696 child_id = uuid4 ()
697697 items = [self ._make_span (child_id , external_parent )]
698698 result = topological_sort_spans (items )
699699 assert len (result ) == 1
700700 assert result [0 ][1 ] == child_id
701+ assert result [0 ][2 ] is None
701702
702703 def test_mixed_roots_and_children (self ):
703704 root_a = uuid4 ()
@@ -891,3 +892,43 @@ async def capture_merge(obj):
891892 assert span_objects [0 ].id == parent_uuid
892893 assert span_objects [1 ].id == child_uuid
893894 assert span_objects [1 ].parent_span_id == parent_uuid
895+
896+ async def test_flush_detaches_span_from_missing_parent (self ):
897+ """A missing parent must not leave an invalid self-referential FK."""
898+ tracer = _make_tracer (flow_id = str (uuid4 ()))
899+ tracer .completed_spans = [
900+ {
901+ "id" : str (uuid4 ()),
902+ "name" : "Orphan Span" ,
903+ "span_type" : SpanType .CHAIN ,
904+ "inputs" : {},
905+ "outputs" : None ,
906+ "start_time" : datetime .now (tz = timezone .utc ),
907+ "end_time" : datetime .now (tz = timezone .utc ),
908+ "latency_ms" : 5 ,
909+ "status" : SpanStatus .OK ,
910+ "error" : None ,
911+ "attributes" : {},
912+ "span_source" : "langchain" ,
913+ "parent_span_id" : uuid4 (),
914+ }
915+ ]
916+
917+ merged_objects = []
918+ mock_session = AsyncMock ()
919+ mock_session .__aenter__ = AsyncMock (return_value = mock_session )
920+ mock_session .__aexit__ = AsyncMock (return_value = False )
921+
922+ async def capture_merge (obj ):
923+ merged_objects .append (obj )
924+
925+ mock_session .merge = capture_merge
926+
927+ with patch ("lfx.services.deps.session_scope" , return_value = mock_session ):
928+ await tracer ._flush_to_database ()
929+
930+ from langflow .services .database .models .traces .model import SpanTable
931+
932+ span_objects = [obj for obj in merged_objects if isinstance (obj , SpanTable )]
933+ assert len (span_objects ) == 1
934+ assert span_objects [0 ].parent_span_id is None
0 commit comments