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:
search_traces {"service_name": "driver", "with_errors": true, "start_time_min": "-24h", "start_time_max": "now"} and got {}
- 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
- Ingest a trace where a span of
service-a has no error status and its child span from service-b has Status.Code = Error.
- Call
search_traces with {"service_name": "service-a", "with_errors": true}. Result: empty.
- 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
What happened?
search_traceswithwith_errors: truereturns 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_errorsas trace-scoped:The handler implements it by adding
error=trueto the span attribute filter: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) callsvalidSpan, 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:
search_traces {"service_name": "driver", "with_errors": true, "start_time_min": "-24h", "start_time_max": "now"}and got{}with_errorsand got 10 traces, every onehas_errors: truein the tool's own outputThe error spans were on
redis-manualchild 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-checkedhas_errorsandget_trace_errors.The output side of the same tool computes
has_errorstrace-scoped, counting error spans across the whole trace (querysvc/summary.go via ErrorSpanCount); the input filter is span-scoped.Steps to reproduce
service-ahas no error status and its child span fromservice-bhasStatus.Code = Error.search_traceswith{"service_name": "service-a", "with_errors": true}. Result: empty.with_errors. Result: the trace is returned withhas_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=trueitself 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 athas_errorsin 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:
error=truemissing OTelStatus.Codeerrors. That translation works here (the memory store matchesStatus.Codeforerror=truesince fix(memory): accept string-form error filters in trace search #8217); the scope of the filter is the problem.search_tracestool silently overwrites user-providederrorattribute whenwith_errorsis true #9176 reportswith_errors: truesilently overwriting an expliciterrorentry inattributes; fix(mcptools): Return error on conflicting search_traces attributes #9177 and fix: validate with_errors against explicit error attribute in search_traces #9236 are open PRs addressing it. That is a conflict-handling problem; the mismatch here occurs with noerrorattribute present.error=trueconvention missing in the ClickHouse backend.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