Skip to content

[Bug]: search_traces describes with_errors as trace-scoped but the filter is span-scoped #9334

Description

@lopster568

What happened?

search_traces with with_errors: true returns zero traces for a service whose traces do contain error spans, whenever the error status is on a downstream service's span rather than on the queried service's own spans. The empty result is indistinguishable from "this service has no errors", and the tool's schema tells the client the opposite of what the filter does.

The schema describes with_errors as trace-scoped:

// cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/types/search_traces.go:28
WithErrors bool `json:"with_errors,omitempty" jsonschema:"If true only return traces containing error spans"`

The handler implements it by adding error=true to the span attribute filter:

// cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/search_traces.go:176-178
if input.WithErrors {
    attributes.PutStr("error", "true")
}

Storage backends apply all search criteria to a single span. The memory store returns a trace only if one span passes every check: validTrace (internal/storage/v2/memory/tenant.go:225-238) calls validSpan, whose error status check is at tenant.go:265-280. Elasticsearch puts every criterion, tags included, into one bool query over per-span documents (buildFindTraceIDsQuery, internal/storage/v2/elasticsearch/tracestore/core/reader.go:499-529).

So the parameter does not mean "only return traces containing error spans". It means "the error status must be on the same span that matches service_name, span_name and attributes". When the errors are on a downstream service's spans, the query returns empty.

The audience for this description is an LLM agent, and the empty result carries no hint. In a Claude agent session captured while benchmarking the MCP tools (v2.20.0 all-in-one running the HotROD demo), the agent issued:

  1. search_traces {"service_name": "driver", "with_errors": true, "start_time_min": "-24h", "start_time_max": "now"} and got {}
  2. the identical query without with_errors and got 10 traces, every one has_errors: true in the tool's own output

The error spans were on redis-manual child spans inside those traces, so no single span matched service=driver plus error status. Nothing in the empty result distinguishes "query too narrow" from "service has no errors"; in this session the agent only avoided the wrong conclusion because it re-ran the query without the flag and cross-checked has_errors and get_trace_errors.

The output side of the same tool computes has_errors trace-scoped, counting error spans across the whole trace (querysvc/summary.go via ErrorSpanCount); the input filter is span-scoped.

Steps to reproduce

  1. Ingest a trace where a span of service-a has no error status and its child span from service-b has Status.Code = Error.
  2. Call search_traces with {"service_name": "service-a", "with_errors": true}. Result: empty.
  3. Call the same query without with_errors. Result: the trace is returned with has_errors: true.

Reproduced on the memory backend; the Elasticsearch query construction has the same shape per the citation above.

Expected behavior

The description and the behavior should agree. The span-scoped conjunction (all criteria matched on one span) is how Jaeger tag search works across backends, and error=true itself is the established convention (#8553), so making the filter genuinely trace-scoped would be a cross-backend semantics decision. The low-risk fix is the description: state that the error filter applies to the same span that matches the other criteria, and point agents at has_errors in the output for trace-scoped error presence. Happy to send a PR for that if maintainers agree the wording is the right side to fix.

Additional context

Nearby issues on the same parameter, none of which cover this:

docs/rfc/0012-mcp-server-extension.md:97 carries the same trace-scoped wording, so the description dates back to the original design.

Jaeger backend version

v2.20.0 (Docker image jaegertracing/jaeger:2.20.0); code citations verified at current main (b3076cc)

SDK

OpenTelemetry Go SDK (HotROD demo app, jaegertracing/example-hotrod:1.71.0)

Pipeline

HotROD -> OTLP -> Jaeger v2 all-in-one

Stogage backend

in-memory (Elasticsearch shares the span-scoped query construction, see citations)

Operating system

Linux

Deployment model

Docker Compose

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions