{"record":{"id":"9b43096a779214e6","repo":"vitessio/vitess","slug":"cannot-adapt-getter-to-synchronized-viper-which-is","errorCode":null,"errorMessage":"cannot adapt getter to synchronized viper which is already watching a config","messagePattern":"cannot adapt getter to synchronized viper which is already watching a config","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/viperutil/internal/sync/sync.go","lineNumber":328,"sourceCode":"\nfunc (v *Viper) SetDefault(key string, value any) {\n\tv.disk.SetDefault(key, value)\n\tv.live.SetDefault(key, value)\n}\n\n// end implementation of registry.Bindable for sync.Viper\n\n// AdaptGetter wraps a get function (matching the signature of\n// viperutil.Options.GetFunc) to be threadsafe with the passed-in synced Viper.\n//\n// It must be called prior to starting a watch on the synced Viper; it will\n// panic if a watch has already been established.\n//\n// This function must be called at most once per key; it will panic if attempting\n// to adapt multiple getters for the same key.\nfunc AdaptGetter[T any](key string, getter func(v *viper.Viper) func(key string) T, v *Viper) func(key string) T {\n\tif v.watchingConfig {\n\t\tpanic(\"cannot adapt getter to synchronized viper which is already watching a config\")\n\t}\n\n\tif _, ok := v.keys[key]; ok {\n\t\tpanic(\"already adapted a getter for key \" + key)\n\t}\n\n\tvar m sync.RWMutex\n\tv.keys[key] = &m\n\n\treturn func(key string) T {\n\t\tm.RLock()\n\t\tdefer m.RUnlock()\n\n\t\treturn getter(v.live)(key)\n\t}\n}\n","sourceCodeStart":310,"sourceCodeEnd":345,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/viperutil/internal/sync/sync.go#L310-L345","documentation":"AdaptGetter registers a dynamic getter for a config key on a synchronized viper wrapper. It panics if the wrapper has already started watching a config file, because adapting getters after the watch begins would leave the getter registry in an inconsistent state. This is an initialization-order contract: all getters must be adapted before NewDynamic establishes the watch.","triggerScenarios":"Calling sync.AdaptGetter after NewDynamic (or any call that sets watchingConfig=true) has already started watching the config; adapting getters lazily at request time instead of during startup initialization.","commonSituations":"Developers add a new dynamic config getter in application code that runs after servenv/logutil init has already called NewDynamic and started watching; refactors move an AdaptGetter call into a later initialization phase or a request handler.","solutions":["Move the AdaptGetter call before the NewDynamic/watch-establishing call in startup order","Adapt all getters during package init or early main() before any config watch starts","If the key is adapted conditionally, ensure every AdaptGetter path runs before the watch (e.g. register all keys upfront with defaults)"],"exampleFix":"// before\nwatched := sync.NewDynamic(\"config\")\ngetter := sync.AdaptGetter[string](\"key\", f, watched) // panics\n// after\ngetter := sync.AdaptGetter[string](\"key\", f, watched)\nwatched := sync.NewDynamic(\"config\")","handlingStrategy":"validation","validationCode":"func canAdapt(v *sync.Viper) bool { return !v.WatchingConfig() } // call before AdaptGetter; ensure registration happens before NewDynamic","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Adapt all getters in one early init phase, before any watch starts","Centralize config getter registration in a single function called from main()","Never adapt getters lazily inside handlers or request paths"],"tags":["go","panic","config","viper","initialization-order"],"backgroundTag":"panic-on-invalid-state","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}