jaegertracing/jaeger · error

span_ids is required and must not be empty

Error message

span_ids is required and must not be empty

What it means

The get_span_details handler limits how many spans may be requested per call (h.maxSpanDetailsPerRequest) to keep responses bounded. This error is thrown in buildQuery when len(input.SpanIDs) exceeds that configured limit, before any query is issued.

Source

Thrown at cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/get_span_details.go:131

		for spanID := range spanIDSet {
			missingIDs = append(missingIDs, spanID)
		}
		output.Error = fmt.Sprintf("spans not found: %v", missingIDs)
	}

	return nil, output, nil
}

// buildQuery converts GetSpanDetailsInput to querysvc.GetTraceParams and returns
// the requested span IDs in canonical lowercase hex form for the lookup set.
func (h *getSpanDetailsHandler) buildQuery(input types.GetSpanDetailsInput) (querysvc.GetTraceParams, []string, error) {
	// Validate input
	if input.TraceID == "" {
		return querysvc.GetTraceParams{}, nil, errors.New("trace_id is required")
	}

	if len(input.SpanIDs) == 0 {
		return querysvc.GetTraceParams{}, nil, errors.New("span_ids is required and must not be empty")
	}

	// Validate span count against configured limit
	if len(input.SpanIDs) > h.maxSpanDetailsPerRequest {
		return querysvc.GetTraceParams{}, nil, fmt.Errorf(
			"span_ids exceeds maximum limit: requested %d, max allowed %d",
			len(input.SpanIDs),
			h.maxSpanDetailsPerRequest,
		)
	}

	traceID, err := parseTraceID(input.TraceID)
	if err != nil {
		return querysvc.GetTraceParams{}, nil, fmt.Errorf("invalid trace_id: %w", err)
	}

	// Validate every span_id up front so a malformed value fails fast instead of
	// triggering a backend query that can never match a real span ID. Collect the

View on GitHub (pinned to 806f444784)

Solutions

  1. Batch the request: split span_ids into chunks at or below the limit and call repeatedly
  2. Deduplicate span_ids before calling (the handler builds a set anyway)
  3. Raise maxSpanDetailsPerRequest configuration if responses are legitimately large
  4. Narrow the span selection to only the spans you actually need

Example fix

// before
out, err := h.Handle(ctx, req, types.GetSpanDetailsInput{TraceID: tid, SpanIDs: allSpanIDs})
// after
for i := 0; i < len(allSpanIDs); i += maxLimit {
    end := min(i+maxLimit, len(allSpanIDs))
    out, err := h.Handle(ctx, req, types.GetSpanDetailsInput{TraceID: tid, SpanIDs: allSpanIDs[i:end]})
    // collect results...
}
Defensive patterns

Strategy: validation

Validate before calling

uniq := make(map[string]struct{}, len(input.SpanIDs))
for _, id := range input.SpanIDs { uniq[id] = struct{}{} }
if len(uniq) > maxSpanDetailsPerRequest {
    return fmt.Errorf("%d unique span IDs exceeds limit %d; batch the request", len(uniq), maxSpanDetailsPerRequest)
}

Try / catch

out, err := handler.Handle(ctx, req, input)
if err != nil && strings.Contains(err.Error(), "exceeds maximum limit") {
    // split input.SpanIDs into chunks <= limit and issue multiple calls
}

Prevention

When it happens

Trigger: Passing more span IDs in the span_ids array than the configured maxSpanDetailsPerRequest cap, e.g. requesting hundreds of spans from a wide trace in one call.

Common situations: Trace with thousands of spans (fan-out/batch jobs) and a client requesting all spans at once; a low configured limit after deployment hardening; duplicated IDs inflating the count.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/ded9a88e7db38db1. Report an issue: GitHub.