{"record":{"id":"cae86703f6834d91","repo":"kubernetes/kubernetes","slug":"adding-job-controller-uid-indexer-w","errorCode":null,"errorMessage":"adding job controller UID indexer: %w","messagePattern":"adding job controller UID indexer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pkg/controller/cronjob/cronjob_controllerv2.go","lineNumber":148,"sourceCode":"\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tjm.enqueueController(obj)\n\t\t},\n\t})\n\n\terr := jobInformer.Informer().AddIndexers(cache.Indexers{\n\t\tjobControllerUIDIndex: func(obj interface{}) ([]string, error) {\n\t\t\tjob, ok := obj.(*batchv1.Job)\n\t\t\tif !ok {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tif controllerRef := metav1.GetControllerOf(job); controllerRef != nil {\n\t\t\t\treturn []string{string(controllerRef.UID)}, nil\n\t\t\t}\n\t\t\treturn nil, nil\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"adding job controller UID indexer: %w\", err)\n\t}\n\n\tmetrics.Register()\n\n\treturn jm, nil\n}\n\n// Run starts the main goroutine responsible for watching and syncing jobs.\nfunc (jm *ControllerV2) Run(ctx context.Context, workers int) {\n\tdefer utilruntime.HandleCrash()\n\n\t// Start event processing pipeline.\n\tjm.broadcaster.StartStructuredLogging(3)\n\tjm.broadcaster.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: jm.kubeClient.CoreV1().Events(\"\")})\n\tdefer jm.broadcaster.Shutdown()\n\n\tlogger := klog.FromContext(ctx)\n\tlogger.Info(\"Starting cronjob controller v2\")","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/kubernetes/kubernetes/blob/94c136764292cc5fac976c0de6587daaea56410f/pkg/controller/cronjob/cronjob_controllerv2.go#L130-L166","documentation":"Returned by NewControllerV2 (or equivalent constructor) when jobInformer.Informer().AddIndexers fails for the jobControllerUIDIndex. The shared informer framework rejects AddIndexers once the informer has already started or when an indexer with the same name is already registered. The CronJob v2 controller relies on this index to look up Jobs by their owner CronJob UID.","triggerScenarios":"Calling AddIndexers on a job informer that has already been started (Run/RunAsync called), or registering jobControllerUIDIndex twice (e.g., constructing two ControllerV2 instances off one shared informer factory).","commonSituations":"Constructing the CronJob controller after the shared informer factory has started; registering the same UID index from two controllers sharing one informer; tests that instantiate the informer synchronously then construct the controller.","solutions":["Register indexers before starting the shared informer factory (before WaitForCacheSync / Run).","Ensure only one ControllerV2 is constructed per job informer, or use a fresh SharedInformerFactory per controller.","In tests, call AddIndexers on the informer before informer.Start()."],"exampleFix":"// before\nfactory.Start(ctx.Done())\njm, _ := cronjob.NewControllerV2(...)\n// after\njm, _ := cronjob.NewControllerV2(...)  // AddIndexers happens here, before Start\nfactory.Start(ctx.Done())","handlingStrategy":"validation","validationCode":"// register indexers BEFORE starting the factory\nfactory := informers.NewSharedInformerFactory(kubeClient, 0)\n// construction must occur prior to factory.Start()\njm, err := cronjob.NewControllerV2(ctx, kubeClient, factory.Batch().V1().Jobs(), factory.Batch().V1().CronJobs())\nif err != nil { return err }\nfactory.Start(ctx.Done())","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always AddIndexers before SharedInformerFactory.Start / WaitForCacheSync.","Construct each controller exactly once per shared informer.","In tests, assert AddIndexers succeeded before invoking the informer."],"tags":["cronjob","informer","indexer","controller-init","shared-cache"],"backgroundTag":null,"analyzedSha":"94c136764292cc5fac976c0de6587daaea56410f","analyzedAt":"2026-08-08T23:58:27.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}