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

  1. Reuse the existing instrument: call m.GetInstrument(name) and use the returned instance instead of creating again.
  2. Rename your new instrument to a unique name if it is genuinely a different metric.
  3. Audit registration paths (controller startup, init functions) and remove duplicate CreateInstrument calls.
  4. 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

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


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