thanos-io/thanos · error · ApiError

invalid label name

Error message

invalid label name: %q

What it means

The label values endpoint validates the label name from the URL path against Prometheus' model.UTF8Validation.IsValidLabelName rules and returns ErrorBadData (HTTP 400) if it fails. Label names must match the Prometheus label-name syntax (legacy [a-zA-Z_][a-zA-Z0-9_]* or UTF-8 quoted form).

Solutions

  1. Use a label name matching the legacy pattern ^[a-zA-Z_][a-zA-Z0-9_]*$
  2. If using UTF-8 label names, ensure the client quotes/escapes them per the UTF-8 naming scheme and the server supports it
  3. URL-encode the label name correctly in the path

Example fix

// before
GET /api/v1/label/my.label/values
// after
GET /api/v1/label/my_label/values
Defensive patterns

Strategy: validation

Validate before calling

func validLegacyLabelName(s string) bool {
    if s == "" { return false }
    for i, r := range s {
        ok := r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (i > 0 && r >= '0' && r <= '9')
        if !ok { return false }
    }
    return true
}

Prevention

When it happens

Trigger: GET /api/v1/label/<name>/values with a name containing invalid characters (dots, dashes, spaces, empty name), or a UTF-8 name sent unquoted to an endpoint expecting legacy validation.

Common situations: Typos in dashboard label selectors; querying metric-name-like strings with slashes; newer UTF-8 label names used against tooling that doesn't quote them.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/b27eccf42d351776. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/query/v1.go:1080

	// Optional stats field in response if parameter "stats" is not empty.
	var qs stats.QueryStats
	if r.FormValue(Stats) != "" {
		qs = stats.NewQueryStats(qry.Stats())
	}
	return &queryData{
		ResultType:    res.Value.Type(),
		Result:        res.Value,
		Stats:         qs,
		QueryAnalysis: analysis,
	}, warnings, nil, qry.Close
}

func (qapi *QueryAPI) labelValues(r *http.Request) (any, []error, *api.ApiError, func()) {
	ctx := r.Context()
	name := route.Param(ctx, "name")

	if !model.UTF8Validation.IsValidLabelName(name) {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid label name: %q", name)}, func() {}
	}

	start, end, err := parseMetadataTimeRange(r, qapi.defaultMetadataTimeRange)
	if err != nil {
		return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
	}

	enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse)
	if apiErr != nil {
		return nil, nil, apiErr, func() {}
	}

	storeDebugMatchers, apiErr := qapi.parseStoreDebugMatchersParam(r)
	if apiErr != nil {
		return nil, nil, apiErr, func() {}
	}

	limit, err := parseLimitParam(r.FormValue("limit"))

View on GitHub (pinned to 35b8b99117)