{"record":{"id":"25ae76e4fc21ea8f","repo":"argoproj/argo-workflows","slug":"instrument-called-s-already-exists","errorCode":null,"errorMessage":"Instrument called %s already exists","messagePattern":"Instrument called (.+?) already exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/telemetry/instrument.go","lineNumber":23,"sourceCode":"\t\"sort\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/otel/metric\"\n\n\t\"github.com/argoproj/argo-workflows/v4/util/help\"\n)\n\ntype Instrument struct {\n\tname        string\n\tdescription string\n\totel        any\n\tmutex       sync.RWMutex\n\tuserdata    any\n}\n\nfunc (m *Metrics) preCreateCheck(name string) error {\n\tif inst := m.GetInstrument(name); inst != nil {\n\t\treturn fmt.Errorf(\"Instrument called %s already exists\", name)\n\t}\n\treturn nil\n}\n\nfunc addHelpLink(name, description string) string {\n\treturn fmt.Sprintf(\"%s %s\", description, help.MetricHelp(name))\n}\n\ntype instrumentType int\n\nconst (\n\tFloat64ObservableGauge instrumentType = iota\n\tFloat64Histogram\n\tFloat64ObservableCounter\n\tInt64ObservableGauge\n\tInt64UpDownCounter\n\tInt64Counter\n)","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/util/telemetry/instrument.go#L5-L41","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ninst, _ := metrics.CreateInstrument(ctx, \"workflows_count\", desc, help)\n// panics/errors when already created\n// after\ninst := metrics.GetInstrument(\"workflows_count\")\nif inst == nil {\n    inst, _ = metrics.CreateInstrument(ctx, \"workflows_count\", desc, help)\n}","handlingStrategy":"type-guard","validationCode":"if inst := metrics.GetInstrument(name); inst != nil {\n    // instrument already registered; reuse it\n    return inst, nil\n}","typeGuard":"func instrumentExists(m *telemetry.Metrics, name string) bool {\n    return m.GetInstrument(name) != nil\n}","tryCatchPattern":"inst, err := metrics.CreateInstrument(ctx, name, desc, help)\nif err != nil && strings.Contains(err.Error(), \"already exists\") {\n    inst = metrics.GetInstrument(name) // reuse existing singleton\n}\nif inst == nil { return err }","preventionTips":["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."],"tags":["metrics","telemetry","instrument","duplicate"],"backgroundTag":"duplicate-metric-instrument","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}