[Bug]: get_critical_path MCP tool corrupts trace offsets when a span has an unset (zero) StartTimestamp
What happened?
The get_critical_path MCP tool (used by AI agents/clients to inspect the critical latency path of a trace) can return badly wrong timing offsets for every segment in a trace, whenever the trace contains at least one span with an unset/zero StartTimestamp (e.g. malformed data or a partially-adjusted span).
The handler computes the trace's overall start time by scanning all spans and tracking the minimum start timestamp, using 0 as a sentinel meaning "no start time seen yet." A span whose StartTimestamp is genuinely zero is indistinguishable from that sentinel. If such a span happens to be iterated before a span with a real, later start time, the real span's start time silently overwrites the correct trace start time of 0.
Because the affected fields (StartOffsetUs, EndOffsetUs) are unsigned integers computed as sectionTime - traceStartTime, once traceStartTime is wrongly inflated this way, the subtraction underflows instead of going negative producing a nonsensical offset near 2^64 instead of a small, correct number. This is returned directly to whatever MCP client/agent called the tool, with no error or warning.
This is order-dependent: it only manifests when the zero-timestamp span happens to be processed before the real spans, since span iteration order (ResourceSpans -> ScopeSpans -> Spans) comes from however the storage backend returned the data, not something the caller controls.
Steps to reproduce
This requires a trace containing a span with an unset/zero StartTimestamp ordered before a span with a real timestamp. Minimal isolated repro of the exact logic in buildOutput() (cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/get_critical_path.go):
starts := []uint64{0, 1_000_000} // zero-timestamp span, then a real span 1s later
var traceStartTime uint64
for _, startTime := range starts {
if traceStartTime == 0 || startTime < traceStartTime {
traceStartTime = startTime
}
}
// traceStartTime ends up 1_000_000, but the correct minimum is 0Concretely via the MCP tool:
- Have a trace where one span never had StartTimestamp set (zero value) and appears first in storage iteration order, alongside a normal span with a real StartTimestamp.
- Call the
get_critical_pathMCP tool for that trace ID. - Inspect
StartOffsetUs/EndOffsetUson the returned segments.
Expected behavior
traceStartTime should equal the true minimum StartTimestamp across all spans in the trace, including a legitimate value of 0, regardless of which order spans are iterated in. StartOffsetUs/EndOffsetUs should always be small, correct, non-underflowed values relative to the trace's actual start.
Relevant log output
No log output : this is a silent data-correctness bug, not a crash or logged error. The returned StartOffsetUs/EndOffsetUs values are simply wrong (potentially near-2^64 due to uint64 underflow).Screenshot
No response
Additional context
Root cause is in buildOutput() in get_critical_path.go:
var traceStartTime uint64
...
if traceStartTime == 0 || startTime < traceStartTime {
traceStartTime = startTime
}Suggested fix: track initialization with an explicit boolean instead of overloading 0 as a sentinel:
var traceStartTime uint64
haveStartTime := false
...
if !haveStartTime || startTime < traceStartTime {
traceStartTime = startTime
haveStartTime = true
}(traceEndTime's existing endTime > traceEndTime check does not have this problem, since it doesn't special-case 0.)
I have a patch with this fix plus a regression test (TestGetCriticalPathHandler_BuildOutput_ZeroStartTimestampSpan) ready to submit as a PR against this issue : happy to open it if this bug report is accepted. I verified the corrected logic in isolation but couldn't run the full test suite in my environment (only had Go 1.22 available; go.mod pins go 1.27.0), so I'd want CI/a maintainer to confirm go test ./cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/... passes before merging.
Found via manual code review of cmd/jaeger/internal/extension/jaegerquery/internal/mcptools/internal/handlers/, not via a live deployment.
Jaeger backend version
No response
SDK
No response
Pipeline
No response
Stogage backend
No response
Operating system
No response
Deployment model
No response
Deployment configs
Source: jaegertracing/jaeger