{"record":{"id":"bf4bf50aba989ed8","repo":"hashicorp/consul","slug":"a-predefined-cache-query-with-name-q-already-exis","errorCode":null,"errorMessage":"a predefined cache query with name %q already exists","messagePattern":"a predefined cache query with name %q already exists","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/controller/controller.go","lineNumber":133,"sourceCode":"\t\tpanic(fmt.Sprintf(\"resource type %q already has a configured watch\", key))\n\t}\n\n\tw := newWatch(watchedType, mapper)\n\n\tfor _, idx := range indexes {\n\t\tw.addIndex(idx)\n\t}\n\n\tctl.watches[key] = w\n\n\treturn ctl\n}\n\n// WithQuery will add a named query to the controllers cache for usage during reconcile or in dependency mappers\nfunc (ctl *Controller) WithQuery(queryName string, fn cache.Query) *Controller {\n\t_, duplicate := ctl.queries[queryName]\n\tif duplicate {\n\t\tpanic(fmt.Sprintf(\"a predefined cache query with name %q already exists\", queryName))\n\t}\n\n\tctl.queries[queryName] = fn\n\treturn ctl\n}\n\n// WithCustomWatch adds a new custom watch. Custom watches do not affect the controller cache.\nfunc (ctl *Controller) WithCustomWatch(source *Source, mapper CustomDependencyMapper) *Controller {\n\tif source == nil {\n\t\tpanic(\"source must not be nil\")\n\t}\n\n\tif mapper == nil {\n\t\tpanic(\"mapper must not be nil\")\n\t}\n\n\tctl.customWatches = append(ctl.customWatches, customWatch{source, mapper})\n\treturn ctl","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/hashicorp/consul/blob/2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e/internal/controller/controller.go#L115-L151","documentation":"Controller.WithQuery registers a named cache query that reconcilers and dependency mappers can look up by string during execution. Query names form the lookup namespace; registering the same name twice panics because the second registration would silently shadow the first and change behavior depending on registration order.","triggerScenarios":"Calling WithQuery(\"name\", fn) twice with the same name on one Controller — typically copy-pasted registration blocks, two modules independently registering a common helper query, or refactored setup code that double-registers.","commonSituations":"Splitting controller wiring across files that both register a shared query; merging controller setups; renaming a query to a name already in use.","solutions":["Give each query a unique name (constants help: const QueryByKind = \"by-kind\")","Consolidate registration of shared queries into a single helper called once","When registering from multiple modules, keep a set of used names and skip or error on duplicates before calling WithQuery"],"exampleFix":"// before\nctl.WithQuery(\"by-name\", byName)\nctl.WithQuery(\"by-name\", byNameV2) // panic: already exists\n\n// after\nctl.WithQuery(\"by-name\", byName)\nctl.WithQuery(\"by-name-v2\", byNameV2)","handlingStrategy":"validation","validationCode":"// deduplicate query names before registering\nnames := map[string]bool{}\nfor name, fn := range queries {\n    if names[name] {\n        return fmt.Errorf(\"duplicate cache query name %q\", name)\n    }\n    names[name] = true\n    ctl.WithQuery(name, fn)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Define query names as exported constants shared by registration and lookup sites","Register shared queries in one helper invoked once per controller","Smoke-test full controller construction in CI so duplicates fail early"],"tags":["go","consul","controller","cache","panic","programmer-error","duplicate-registration"],"backgroundTag":null,"analyzedSha":"2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e","analyzedAt":"2026-08-15T19:19:47.700Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}