vitessio/vitess · error

cannot adapt getter to synchronized viper which is already w

Error message

cannot adapt getter to synchronized viper which is already watching a config

What it means

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.

Source

Thrown at go/viperutil/internal/sync/sync.go:328

func (v *Viper) SetDefault(key string, value any) {
	v.disk.SetDefault(key, value)
	v.live.SetDefault(key, value)
}

// end implementation of registry.Bindable for sync.Viper

// AdaptGetter wraps a get function (matching the signature of
// viperutil.Options.GetFunc) to be threadsafe with the passed-in synced Viper.
//
// It must be called prior to starting a watch on the synced Viper; it will
// panic if a watch has already been established.
//
// This function must be called at most once per key; it will panic if attempting
// to adapt multiple getters for the same key.
func AdaptGetter[T any](key string, getter func(v *viper.Viper) func(key string) T, v *Viper) func(key string) T {
	if v.watchingConfig {
		panic("cannot adapt getter to synchronized viper which is already watching a config")
	}

	if _, ok := v.keys[key]; ok {
		panic("already adapted a getter for key " + key)
	}

	var m sync.RWMutex
	v.keys[key] = &m

	return func(key string) T {
		m.RLock()
		defer m.RUnlock()

		return getter(v.live)(key)
	}
}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Move the AdaptGetter call before the NewDynamic/watch-establishing call in startup order
  2. Adapt all getters during package init or early main() before any config watch starts
  3. If the key is adapted conditionally, ensure every AdaptGetter path runs before the watch (e.g. register all keys upfront with defaults)

Example fix

// before
watched := sync.NewDynamic("config")
getter := sync.AdaptGetter[string]("key", f, watched) // panics
// after
getter := sync.AdaptGetter[string]("key", f, watched)
watched := sync.NewDynamic("config")
Defensive patterns

Strategy: validation

Validate before calling

func canAdapt(v *sync.Viper) bool { return !v.WatchingConfig() } // call before AdaptGetter; ensure registration happens before NewDynamic

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/9b43096a779214e6. Report an issue: GitHub.