vitessio/vitess · error
cannot Notify after starting to watch a config
Error message
cannot Notify after starting to watch a config
What it means
In go/viperutil/internal/sync, Viper.Notify registers a channel to receive change notifications and must be called before any Watch is established. Once watchingConfig is true the subscription set is frozen, so calling Notify afterwards panics to prevent a subscriber that would silently miss notifications. The doc comment on the method states this contract explicitly.
Source
Thrown at go/viperutil/internal/sync/sync.go:261
defer v.m.Unlock()
v.live.SetConfigFile(v.disk.ConfigFileUsed())
return v.live.WriteConfig()
}
// Notify adds a subscription that this synced viper will attempt to notify on
// config changes, after the updated config has been copied over from disk to
// live.
//
// Analogous to signal.Notify, notifications are sent non-blocking, so users
// should account for this when consuming from the channel they've provided.
//
// This function must be called prior to setting up a Watch; it will panic if a
// a watch has already been established on this synced Viper.
func (v *Viper) Notify(ch chan<- struct{}) {
if v.watchingConfig {
panic("cannot Notify after starting to watch a config")
}
v.subscribers = append(v.subscribers, ch)
}
// AllSettings returns the current live settings.
func (v *Viper) AllSettings() map[string]any {
v.m.Lock()
defer v.m.Unlock()
return v.live.AllSettings()
}
func (v *Viper) loadFromDisk() {
v.m.Lock()
defer v.m.Unlock()
// Reset v.live so explicit Set calls don't win over what's just changed onView on GitHub (pinned to 01a25a7d17)
Solutions
- Move all Notify calls before the Watch call — subscribe every consumer during initialization, then start watching last
- Give each package its own synced Viper instance so subscription and watching are independently ordered
- Wrap the shared setup in a single constructor that registers all subscribers and then starts the watch once
Example fix
// before watcher.Watch(v) // starts watching v.Notify(myCh) // panics // after v.Notify(myCh) // subscribe first watcher.Watch(v) // then start watching
Defensive patterns
Strategy: validation
Validate before calling
// Expose a guarded subscribe on your wrapper
func Subscribe(v *sync.Viper, ch chan<- struct{}) error {
if v.WatchingConfig() { // or track the flag yourself
return errors.New("cannot Notify after Watch; subscribe during init")
}
v.Notify(ch)
return nil
} Try / catch
func() {
defer func() {
if r := recover(); r != nil && strings.Contains(fmt.Sprint(r), "cannot Notify after starting") {
log.Fatalf("config subscriber registered too late: %v", r)
}
}()
v.Notify(ch)
}() Prevention
- Register all Notify subscribers in one init/constructor phase, then call Watch last
- Do not lazily subscribe on first use; subscribe eagerly at startup
- Give each package its own synced Viper or centralize watch setup in a single bootstrap function
When it happens
Trigger: Calling syncedViper.Notify(ch) after Watch/Configure has already started watching the config — e.g. adding a subscriber in a later init or lazily on first use, while an earlier component already called Watch on the same synced Viper.
Common situations: Multiple packages share one synced Viper: package A sets up a watch during init, package B later tries to subscribe to changes and panics at startup or on first config access.
Related errors
- GetFuncForType does not support array types
- GetFuncForType does not support channel types
- GetFuncForType does not support function types
- GetFuncForType does not support interface types (specify a s
- no default GetFunc for type %T; call Configure with a custom
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/5631cbcdd46094e1.
Report an issue: GitHub.