jaegertracing/jaeger · error

aggregation bucket has a non-string key: %w

Error message

aggregation bucket has a non-string key: %w

What it means

When decoding aggregation response buckets, Jaeger expects each bucket's 'key' to be a JSON string (aggregations are on traceID or serviceName). UnmarshalJSON fails the decode with this wrapped error if 'key' is present but not a string, instead of yielding an empty key that callers would treat as a valid ID.

Source

Thrown at internal/storage/elasticsearch/esclient/aggregation.go:122

// and any nested sub-aggregations (reached through the promoted Aggregations
// accessors).
type AggregationBucket struct {
	Key      string
	DocCount int
	Aggregations
}

func (b *AggregationBucket) UnmarshalJSON(data []byte) error {
	raw := map[string]json.RawMessage{}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}
	// Bucket keys are strings for the fields we aggregate (traceID, serviceName).
	// A present-but-non-string key means a mapping regression, so fail the decode
	// rather than silently yield an empty key that callers treat as a valid ID.
	if k, ok := raw["key"]; ok {
		if err := json.Unmarshal(k, &b.Key); err != nil {
			return fmt.Errorf("aggregation bucket has a non-string key: %w", err)
		}
		delete(raw, "key")
	}
	if dc, ok := raw["doc_count"]; ok {
		if err := json.Unmarshal(dc, &b.DocCount); err != nil {
			return err
		}
		delete(raw, "doc_count")
	}
	b.Aggregations = raw
	return nil
}

func (b AggregationBucket) MarshalJSON() ([]byte, error) {
	return marshalBucket(b.Key, b.DocCount, b.Aggregations)
}

// HistogramResult holds the buckets of a date_histogram aggregation.

View on GitHub (pinned to 806f444784)

Solutions

  1. Fix the index mapping so the aggregated field is a keyword/string type
  2. Reindex affected indices so bucket keys are strings
  3. Check which field the aggregation targets and that the backend version emits string keys

Example fix

// before (mapping)
"serviceVersion": { "type": "long" }
// after
"serviceVersion": { "type": "keyword" }
Defensive patterns

Strategy: try-catch

Validate before calling

// before decoding, sanity-check mapping of the aggregated field
var m map[string]any
json.Unmarshal(mappingJSON, &m)
// ensure the aggregated field type is 'keyword', not numeric

Type guard

func isStringKey(raw json.RawMessage) bool {
    var s string
    return json.Unmarshal(raw, &s) == nil
}

Try / catch

var buckets []aggregationBucket
if err := json.Unmarshal(respBody, &buckets); err != nil {
    if strings.Contains(err.Error(), "non-string key") {
        // alert: index mapping regression on the aggregated field
    }
    return fmt.Errorf("decoding aggregation response: %w", err)
}

Prevention

When it happens

Trigger: Calling json.Unmarshal/Decode on an aggregation response whose bucket objects contain a non-string 'key' (e.g. a number or object) while unmarshalling into aggregationBucket.

Common situations: Elasticsearch mapping regression so the aggregated field is indexed as a number/keyword-object; querying an unexpected field where keys are numeric.

Related errors


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