argoproj/argo-workflows · error

invalid metric spec

Error message

invalid metric spec

What it means

createCustomMetric dispatches on the metricSpec's metric type; if the type is none of gauge/counter/histogram it returns 'invalid metric spec'. Called from ensureBaseMetric when the instrument does not yet exist, so it fires on the first upsert of a malformed spec.

Source

Thrown at workflow/metrics/metrics_custom.go:306

		}
		return nil
	case metricType == wfv1.MetricTypeGauge:
		return m.createCustomGauge(metricSpec)
	case metricType == wfv1.MetricTypeHistogram:
		return m.CreateInstrument(telemetry.Float64Histogram, metricSpec.Name, metricSpec.Help, "{item}", telemetry.WithDefaultBuckets(metricSpec.Histogram.GetBuckets()))
	case metricType == wfv1.MetricTypeCounter:
		err := m.CreateInstrument(telemetry.Float64ObservableCounter, metricSpec.Name, metricSpec.Help, "{item}")
		if err != nil {
			return err
		}
		inst := m.GetInstrument(metricSpec.Name)
		if customUserData(inst, false) == nil {
			inst.SetUserdata(newUserData())
		}
		customInst := customInstrument{Instrument: inst}
		return inst.RegisterCallback(m.Metrics, customInst.customCallback)
	default:
		return fmt.Errorf("invalid metric spec")
	}
}

func (m *Metrics) createCustomGauge(metricSpec *wfv1.Prometheus) error {
	err := m.CreateInstrument(telemetry.Float64ObservableGauge, metricSpec.Name, metricSpec.Help, "{item}")
	if err != nil {
		return err
	}
	inst := m.GetInstrument(metricSpec.Name)
	if customUserData(inst, false) == nil {
		inst.SetUserdata(newUserData())
	}
	customInst := customInstrument{Instrument: inst}
	return inst.RegisterCallback(m.Metrics, customInst.customCallback)
}

func (m *Metrics) runCustomGC(ttl time.Duration) {
	m.IterateROInstruments(func(baseMetric *telemetry.Instrument) {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Declare exactly one of gauge:, counter:, or histogram: in the metric spec
  2. Validate the workflow with argo lint before submission to catch missing type blocks
  3. Fix indentation so the type block is nested under the correct metric item

Example fix

# before
- name: events
  counter:
  value: "1"
# after
- name: events
  counter:
    value: "1"
Defensive patterns

Strategy: validation

Validate before calling

// check the type stanza is nested under the metric item before submit
for _, m := range tmpl.Metrics.Prometheus {
	if m.Gauge == nil && m.Counter == nil && m.Histogram == nil {
		return fmt.Errorf("metric %s has no type block", m.Name)
	}
}

Prevention

When it happens

Trigger: ensureBaseMetric -> createCustomMetric with a wfv1.Prometheus spec lacking any recognized type stanza (gauge/counter/histogram all nil), typically because the workflow metric entry is empty or the type block failed to parse.

Common situations: Empty metrics entries in workflow YAML; wrong indentation placing gauge/counter/histogram outside the metric item; building specs in code without setting a type field.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/3c7676717f07ea76. Report an issue: GitHub.