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
- Declare exactly one of gauge:, counter:, or histogram: in the metric spec
- Validate the workflow with argo lint before submission to catch missing type blocks
- 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
- Check YAML indentation: gauge/counter/histogram must be a child of the metric item
- Run argo lint in CI
- Reject empty metrics entries in templates via review/lint
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
- invalid custom metric type
- CodeBadRequest
- successCondition, failureCondition and outputs are not suppo
- %s
- output parameters must have a valueFrom specified
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/3c7676717f07ea76.
Report an issue: GitHub.