jaegertracing/jaeger · error
%s must be before %s
Error message
%s must be before %s
What it means
After both timestamps parse, Jaeger enforces a strict ordering: query.startTimeMin must be strictly before query.startTimeMax. Equal or inverted bounds are rejected with this message.
Source
Thrown at cmd/jaeger/internal/extension/jaegerquery/internal/apiv3/query_parser.go:114
}
queryParams.Filter = filter
}
timeMinStr, timeMinParam := getQueryParam(q, paramTimeMin, paramTimeMinDeprecated)
timeMaxStr, timeMaxParam := getQueryParam(q, paramTimeMax, paramTimeMaxDeprecated)
if timeMinStr == "" || timeMaxStr == "" {
return nil, fmt.Errorf("%s and %s are required", paramTimeMin, paramTimeMax)
}
timeMinParsed, err := time.Parse(time.RFC3339Nano, timeMinStr)
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", timeMinParam, err)
}
timeMaxParsed, err := time.Parse(time.RFC3339Nano, timeMaxStr)
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", timeMaxParam, err)
}
if !timeMinParsed.Before(timeMaxParsed) {
return nil, fmt.Errorf("%s must be before %s", paramTimeMin, paramTimeMax)
}
queryParams.StartTimeMin = timeMinParsed
queryParams.StartTimeMax = timeMaxParsed
n, searchDepthParam := getQueryParam(q, paramSearchDepth, paramSearchDepthDeprecated)
if n == "" {
n = q.Get(paramNumTraces)
searchDepthParam = paramNumTraces
}
if n != "" {
searchDepth, err := strconv.Atoi(n)
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", searchDepthParam, err)
}
queryParams.SearchDepth = searchDepth
} else {
queryParams.SearchDepth = defaultSearchDepth
}View on GitHub (pinned to 806f444784)
Solutions
- Swap the values so the earlier timestamp is in query.startTimeMin.
- Ensure min < max strictly; if you need a point-in-time lookup use trace_id lookup endpoints instead.
- Add client-side validation before sending: new Date(min) < new Date(max).
Example fix
// before (swapped) query.startTimeMin=2026-01-02T00:00:00Z&query.startTimeMax=2026-01-01T00:00:00Z // after query.startTimeMin=2026-01-01T00:00:00Z&query.startTimeMax=2026-01-02T00:00:00Z
Defensive patterns
Strategy: validation
Validate before calling
func checkRange(minStr, maxStr string) error {
min, err1 := time.Parse(time.RFC3339Nano, minStr)
max, err2 := time.Parse(time.RFC3339Nano, maxStr)
if err1 != nil || err2 != nil { return errors.New("bad timestamps") }
if !min.Before(max) { return errors.New("startTimeMin must be before startTimeMax") }
return nil
} Try / catch
if resp.StatusCode == 400 {
return fmt.Errorf("time range rejected: %s", mustRead(resp.Body))
} Prevention
- Construct ranges via a helper that takes (start, end) in order, never min/max as separate ad-hoc params.
- Clamp end = max(start+epsilon, end) when both bounds may coincide.
- Add a unit test that min == max is rejected before sending.
When it happens
Trigger: GET /api/v3/traces where query.startTimeMin >= query.startTimeMax - e.g. both set to the same instant, or min/max swapped by a client that builds the range from unsorted values.
Common situations: Swapped variables when constructing the URL; a zero-length window (min == max) which the API does not allow; clients that clamp both bounds to 'now'.
Related errors
- %s and %s are required
- unsupported span kind: '%s'
- unable to parse param '%s': %w
- SearchDepth must not be greater than %d
- trace_id is required
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/7691147384bd5586.
Report an issue: GitHub.