argoproj/argo-workflows · error
Instrument called %s already exists
Error message
Instrument called %s already exists
What it means
Metrics.preCreateCheck rejects CreateInstrument calls when an instrument with the same name already exists in the Metrics registry. Prometheus/OTel instruments are singletons per name, so duplicate creation would be a programming error; the check returns this error instead of overwriting.
Source
Thrown at util/telemetry/instrument.go:23
"sort"
"sync"
"go.opentelemetry.io/otel/metric"
"github.com/argoproj/argo-workflows/v4/util/help"
)
type Instrument struct {
name string
description string
otel any
mutex sync.RWMutex
userdata any
}
func (m *Metrics) preCreateCheck(name string) error {
if inst := m.GetInstrument(name); inst != nil {
return fmt.Errorf("Instrument called %s already exists", name)
}
return nil
}
func addHelpLink(name, description string) string {
return fmt.Sprintf("%s %s", description, help.MetricHelp(name))
}
type instrumentType int
const (
Float64ObservableGauge instrumentType = iota
Float64Histogram
Float64ObservableCounter
Int64ObservableGauge
Int64UpDownCounter
Int64Counter
)View on GitHub (pinned to 35bff19146)
Solutions
- Reuse the existing instrument: call m.GetInstrument(name) and use the returned instance instead of creating again.
- Rename your new instrument to a unique name if it is genuinely a different metric.
- Audit registration paths (controller startup, init functions) and remove duplicate CreateInstrument calls.
- In tests, create a fresh Metrics instance per test or assert the existing instrument matches your config.
Example fix
// before
inst, _ := metrics.CreateInstrument(ctx, "workflows_count", desc, help)
// panics/errors when already created
// after
inst := metrics.GetInstrument("workflows_count")
if inst == nil {
inst, _ = metrics.CreateInstrument(ctx, "workflows_count", desc, help)
} Defensive patterns
Strategy: type-guard
Validate before calling
if inst := metrics.GetInstrument(name); inst != nil {
// instrument already registered; reuse it
return inst, nil
} Type guard
func instrumentExists(m *telemetry.Metrics, name string) bool {
return m.GetInstrument(name) != nil
} Try / catch
inst, err := metrics.CreateInstrument(ctx, name, desc, help)
if err != nil && strings.Contains(err.Error(), "already exists") {
inst = metrics.GetInstrument(name) // reuse existing singleton
}
if inst == nil { return err } Prevention
- Register all instruments in a single, centralized init path.
- Prefer GetInstrument-first, create-if-missing idiom at call sites.
- Use unique, namespaced metric names in the telemetry registry.
- In tests, instantiate a fresh Metrics per test case.
When it happens
Trigger: Calling metrics.CreateInstrument(name, ...) with a name previously registered on the same Metrics instance — e.g. double initialization at controller startup, two code paths creating the same instrument, or a test reusing a Metrics object across cases without reset.
Common situations: Registering the same instrument in two init paths; adding a new metric whose name collides with an existing one in the telemetry registry; tests that construct instruments per test case on a shared Metrics; hot-reload paths re-running registration.
Related errors
- Didn't successfully replace docs in %s
- %s
- %s
- expected no more than 1 workflow, got %d
- help for metric %s is already set to %s, it cannot be change
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/25ae76e4fc21ea8f.
Report an issue: GitHub.