{"record":{"id":"5631cbcdd46094e1","repo":"vitessio/vitess","slug":"cannot-notify-after-starting-to-watch-a-config","errorCode":null,"errorMessage":"cannot Notify after starting to watch a config","messagePattern":"cannot Notify after starting to watch a config","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/viperutil/internal/sync/sync.go","lineNumber":261,"sourceCode":"\tdefer v.m.Unlock()\n\n\tv.live.SetConfigFile(v.disk.ConfigFileUsed())\n\n\treturn v.live.WriteConfig()\n}\n\n// Notify adds a subscription that this synced viper will attempt to notify on\n// config changes, after the updated config has been copied over from disk to\n// live.\n//\n// Analogous to signal.Notify, notifications are sent non-blocking, so users\n// should account for this when consuming from the channel they've provided.\n//\n// This function must be called prior to setting up a Watch; it will panic if a\n// a watch has already been established on this synced Viper.\nfunc (v *Viper) Notify(ch chan<- struct{}) {\n\tif v.watchingConfig {\n\t\tpanic(\"cannot Notify after starting to watch a config\")\n\t}\n\n\tv.subscribers = append(v.subscribers, ch)\n}\n\n// AllSettings returns the current live settings.\nfunc (v *Viper) AllSettings() map[string]any {\n\tv.m.Lock()\n\tdefer v.m.Unlock()\n\n\treturn v.live.AllSettings()\n}\n\nfunc (v *Viper) loadFromDisk() {\n\tv.m.Lock()\n\tdefer v.m.Unlock()\n\n\t// Reset v.live so explicit Set calls don't win over what's just changed on","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/viperutil/internal/sync/sync.go#L243-L279","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nwatcher.Watch(v) // starts watching\nv.Notify(myCh)   // panics\n// after\nv.Notify(myCh)   // subscribe first\nwatcher.Watch(v) // then start watching","handlingStrategy":"validation","validationCode":"// Expose a guarded subscribe on your wrapper\nfunc Subscribe(v *sync.Viper, ch chan<- struct{}) error {\n    if v.WatchingConfig() { // or track the flag yourself\n        return errors.New(\"cannot Notify after Watch; subscribe during init\")\n    }\n    v.Notify(ch)\n    return nil\n}","typeGuard":null,"tryCatchPattern":"func() {\n    defer func() {\n        if r := recover(); r != nil && strings.Contains(fmt.Sprint(r), \"cannot Notify after starting\") {\n            log.Fatalf(\"config subscriber registered too late: %v\", r)\n        }\n    }()\n    v.Notify(ch)\n}()","preventionTips":["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"],"tags":["go","viper","config","lifecycle","panic"],"backgroundTag":"invalid-lifecycle-order","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}