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

  1. Swap the values so the earlier timestamp is in query.startTimeMin.
  2. Ensure min < max strictly; if you need a point-in-time lookup use trace_id lookup endpoints instead.
  3. 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

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


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