microsoft/aspire · error · InvalidOperationException
Circular loop detected for span
Error message
Circular loop detected for span '{span.SpanId}' with parent '{span.ParentSpanId}'. What it means
OtlpTrace.AddSpan validates parent/child relationships and throws this InvalidOperationException when a newly inserted span creates a circular parent chain (a span is its own ancestor). Circular references would cause infinite loops or broken tree rendering, so the span is removed from Spans and the error is thrown. This guards against malformed trace data where ParentSpanId points back into the span's own ancestry.
Solutions
- Fix the emitting instrumentation so ParentSpanId always refers to an ancestor, never the span itself or its descendant.
- Validate span graphs before import: detect and break cycles by nulling the offending ParentSpanId.
- If the producer is a custom exporter/library, verify it copies the correct parent context (Activity.ParentSpanId / parent trace flags) when creating spans.
Example fix
// before: self-referential parent
var span = new OtlpSpan { SpanId = id, ParentSpanId = id };
// after: parent must be a different, pre-existing span (or empty for a root)
var span = new OtlpSpan { SpanId = id, ParentSpanId = parentId != id ? parentId : null }; Defensive patterns
Strategy: validation
Validate before calling
// verify the parent chain is acyclic before adding bool isSelfParent = span.SpanId == span.ParentSpanId; bool parentExists = string.IsNullOrEmpty(span.ParentSpanId) || trace.Spans.Any(s => s.SpanId == span.ParentSpanId);
Try / catch
try
{
trace.AddSpan(span);
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Circular loop detected"))
{
logger.LogWarning(ex, "Rejecting span {SpanId} with cyclic parent {ParentId}.", span.SpanId, span.ParentSpanId);
} Prevention
- Never set a span's ParentSpanId to its own SpanId or to one of its descendants.
- Verify context propagation code copies the correct parent id in async/ambient flows.
- Validate span graphs in fixtures before feeding them into AddSpan.
When it happens
Trigger: AddSpan throws when HasCircularReference(span) is true after inserting the span, i.e. span.ParentSpanId resolves (directly or transitively) back to span.SpanId. Caused by telemetry producers emitting inconsistent parent ids, e.g. a span listing itself as parent, or A/B cycles from buggy context propagation.
Common situations: Buggy custom context propagation overwriting parent ids; span re-parenting logic in middleware producing a cycle; test fixtures hand-crafting span graphs with wrong parent ids; producer bugs after sampling/context corruption in async flows.
Related errors
- Duplicate span id ' ' detected.
- Histogram data point has bucket counts without any explicit…
- Metric data point value must be finite.
- No trace found in OTLP data.
- A circular lifetime reference was detected for resource
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/26bba7996f208b4c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Dashboard/Otlp/Model/OtlpTrace.cs:61
{
throw new InvalidOperationException($"Duplicate span id '{span.SpanId}' detected.");
}
var insertIndex = 0;
for (var i = Spans.Count - 1; i >= 0; i--)
{
if (span.StartTime > Spans[i].StartTime)
{
insertIndex = i + 1;
break;
}
}
Spans.Insert(insertIndex, span);
if (HasCircularReference(span))
{
Spans.Remove(span);
throw new InvalidOperationException($"Circular loop detected for span '{span.SpanId}' with parent '{span.ParentSpanId}'.");
}
_duration = null;
if (string.IsNullOrEmpty(span.ParentSpanId))
{
// There should only be one span with no parent span ID.
// Incase there isn't, the first span with no parent span ID is considered to be the root.
foreach (var existingSpan in Spans)
{
if (string.IsNullOrEmpty(existingSpan.ParentSpanId))
{
_rootSpan = existingSpan;
FullName = BuildFullName(existingSpan);
break;
}
}
}View on GitHub (pinned to 25830f84bd)