@@ -378,7 +378,31 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS
378378 // reason (if any) determines whether `cancelled` is an
379379 // expected outcome (explicit_stop → status OK) or a real
380380 // error (client_disconnect / unknown → status ERROR).
381- activeOtelRoot . finish ( rootOutcome , rootError , cancelReason )
381+ //
382+ // Belt-and-suspenders: if `finish()` itself throws (e.g. an
383+ // argument in the TDZ, a bad attribute, a regression in
384+ // status-setting), fall back to `span.end()` directly. A
385+ // root that never ends leaves every child orphaned in Tempo
386+ // under a phantom parent; force-ending it keeps the trace
387+ // shape intact even when the pretty-finalize path is
388+ // broken. The error is logged so Loki greps surface the
389+ // regression instead of it silently costing us trace
390+ // fidelity for hours.
391+ try {
392+ activeOtelRoot . finish ( rootOutcome , rootError , cancelReason )
393+ } catch ( finishError ) {
394+ logger . error ( `[${ requestId } ] activeOtelRoot.finish threw; force-ending root span` , {
395+ error : finishError instanceof Error ? finishError . message : String ( finishError ) ,
396+ } )
397+ try {
398+ activeOtelRoot . span . end ( )
399+ } catch {
400+ // Already ended or an OTel internal failure — nothing
401+ // more we can do. The export pipe has already had its
402+ // chance; swallow to avoid masking the original error
403+ // path.
404+ }
405+ }
382406 }
383407 } )
384408 } ,
0 commit comments