{"record":{"id":"2975ebf1f03a202c","repo":"hashicorp/consul","slug":"resource-type-q-already-has-a-configured-watch","errorCode":null,"errorMessage":"resource type %q already has a configured watch","messagePattern":"resource type %q already has a configured watch","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/controller/controller.go","lineNumber":115,"sourceCode":"// WithReconciler changes the controller's reconciler.\nfunc (ctl *Controller) WithReconciler(reconciler Reconciler) *Controller {\n\tif reconciler == nil {\n\t\tpanic(\"reconciler must not be nil\")\n\t}\n\n\tctl.reconciler = reconciler\n\treturn ctl\n}\n\n// WithWatch enables watching of the specified resource type and mapping it to the managed type\n// via the provided DependencyMapper. Extra cache indexes to calculate on the watched type\n// may also be provided.\nfunc (ctl *Controller) WithWatch(watchedType *pbresource.Type, mapper DependencyMapper, indexes ...*index.Index) *Controller {\n\tkey := resource.ToGVK(watchedType)\n\n\t_, alreadyWatched := ctl.watches[key]\n\tif alreadyWatched {\n\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))","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/hashicorp/consul/blob/2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e/internal/controller/controller.go#L97-L133","documentation":"Controller.WithWatch registers a watch on a resource type, keyed by the type's Group-Version-Kind (resource.ToGVK). One controller can watch each type only once; a second WithWatch for the same GVK panics, because two dependency mappers for one type would race and one would be silently discarded.","triggerScenarios":"Calling WithWatch twice with the same pbresource.Type (same Group/Kind/Version) on one Controller instance — e.g. copy-pasted registration blocks, a shared 'addCommonWatches' helper invoked twice, or merging two controllers that both watched the type.","commonSituations":"Refactoring controller setup into helper functions that get called from multiple paths; onboarding a watched type that another part of setup already registered.","solutions":["Delete or merge the duplicate WithWatch so each GVK is registered once","If two mappings are genuinely needed, combine them into a single DependencyMapper for that watch","When building watches dynamically, keep a set of registered GVK strings and skip duplicates before calling WithWatch"],"exampleFix":"// before\nctl.WithWatch(res.ServiceRouterType, mapper, idx)\nctl.WithWatch(res.ServiceRouterType, otherMapper) // panic: already has a configured watch\n\n// after\nctl.WithWatch(res.ServiceRouterType, combinedMapper, idx) // one watch, one mapper","handlingStrategy":"validation","validationCode":"// track watched GVKs when building controllers dynamically\nwatched := map[string]bool{}\nfor _, w := range watchSpecs {\n    key := resource.ToGVK(w.Type)\n    if watched[key] {\n        continue // or return an error\n    }\n    watched[key] = true\n    ctl.WithWatch(w.Type, w.Mapper, w.Indexes...)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register each watched type from exactly one place; keep a single watch-spec list","Merge multiple mappers for one type into a single DependencyMapper","Add a setup unit test that builds the whole controller; duplicates panic in CI"],"tags":["go","consul","controller","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"}