{"record":{"id":"e521009f410b9d52","repo":"tailscale/tailscale","slug":"error-watching-config-secret-q-v","errorCode":null,"errorMessage":"error watching config Secret %q: %v","messagePattern":"error watching config Secret %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/k8s-proxy/internal/config/config.go","lineNumber":243,"sourceCode":"\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch ev.Type {\n\t\t\tcase watch.Added, watch.Modified:\n\t\t\t\t// New config available to load.\n\t\t\t\tvar ok bool\n\t\t\t\tsecret, ok = ev.Object.(*corev1.Secret)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\"unexpected object type %T in watch event for config Secret %q\", ev.Object, secretName)\n\t\t\t\t}\n\t\t\t\tif secret == nil || secret.Data == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif err := ld.configFromSecret(ctx, secret); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reloading config Secret %q: %v\", secret.Name, err)\n\t\t\t\t}\n\t\t\tcase watch.Error:\n\t\t\t\treturn fmt.Errorf(\"error watching config Secret %q: %v\", secretName, ev.Object)\n\t\t\tdefault:\n\t\t\t\t// Ignore, no action required.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (ld *configLoader) configFromSecret(ctx context.Context, s *corev1.Secret) error {\n\tb := s.Data[kubetypes.KubeAPIServerConfigFile]\n\tif len(b) == 0 {\n\t\treturn fmt.Errorf(\"config Secret %q does not contain expected config in key %q\", s.Name, kubetypes.KubeAPIServerConfigFile)\n\t}\n\n\tif err := ld.reloadConfig(ctx, b); err != nil {\n\t\treturn err\n\t}\n","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/k8s-proxy/internal/config/config.go#L225-L261","documentation":"Returned when the watch stream delivers a watch.Error event for the config Secret. This is the API server reporting an error on the watch itself (as opposed to a Go-level error from the client); ev.Object is formatted with %v.","triggerScenarios":"The watch request fails server-side: 410 Gone when the resourceVersion is too old to resume, forbidden responses after an RBAC change mid-watch, or API server internal errors surfaced as watch Error events.","commonSituations":"The Secret churned heavily so the watch fell behind and the server expired it; RBAC tightened while the proxy was running; etcd/API server instability.","solutions":["Treat as transient first: restart the proxy pod, which establishes a fresh watch with a current resourceVersion","Re-check RBAC (watch verb) and cluster events for API server errors around that time","If 410 Gone recurs, reduce churn on the Secret or move to a dedicated config object"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for {\n\terr := cfgLoader.WatchConfig(ctx, path)\n\tif err == nil || errors.Is(err, context.Canceled) {\n\t\treturn err\n\t}\n\t// watch.Error events (410 Gone, RBAC) are usually recoverable with a fresh watch\n\tlogger.Warnf(\"secret watch error (%v); restarting watch\", err)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tcase <-time.After(5 * time.Second):\n\t}\n}","preventionTips":["Treat watch Error events as transient: re-list and re-watch from the current resourceVersion","Reduce churn on the watched Secret to avoid resourceVersion expiry","Monitor RBAC changes that could invalidate long-running watches"],"tags":["kubernetes","watch","rbac","resilience"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}