hashicorp/consul · error
source must not be nil
Error message
source must not be nil
What it means
Controller.WithCustomWatch attaches a watch backed by an external event Source (it feeds the controller directly instead of the shared cache). A nil Source is rejected with a panic at setup time: the controller would have nothing to consume and every attempt to start the watch would nil-panic inside Run().
Source
Thrown at internal/controller/controller.go:143
return ctl
}
// WithQuery will add a named query to the controllers cache for usage during reconcile or in dependency mappers
func (ctl *Controller) WithQuery(queryName string, fn cache.Query) *Controller {
_, duplicate := ctl.queries[queryName]
if duplicate {
panic(fmt.Sprintf("a predefined cache query with name %q already exists", queryName))
}
ctl.queries[queryName] = fn
return ctl
}
// WithCustomWatch adds a new custom watch. Custom watches do not affect the controller cache.
func (ctl *Controller) WithCustomWatch(source *Source, mapper CustomDependencyMapper) *Controller {
if source == nil {
panic("source must not be nil")
}
if mapper == nil {
panic("mapper must not be nil")
}
ctl.customWatches = append(ctl.customWatches, customWatch{source, mapper})
return ctl
}
// WithLogger changes the controller's logger.
func (ctl *Controller) WithLogger(logger hclog.Logger) *Controller {
if logger == nil {
panic("logger must not be nil")
}
ctl.logger = logger
return ctlView on GitHub (pinned to 2397ff0d76)
Solutions
- Construct the Source first and pass it: WithCustomWatch(buildSource(cfg), mapper)
- If a constructor can fail, handle the error and abort setup instead of passing nil
- Order setup so WithCustomWatch is only called once the source is fully built
Example fix
// before var src *controller.Source ctl.WithCustomWatch(src, mapper) // panic: source must not be nil // after src := controller.NewSource(...) // construct first ctl.WithCustomWatch(src, mapper)
Defensive patterns
Strategy: validation
Validate before calling
// build and validate the source before wiring
src, err := buildSource(cfg)
if err != nil {
return fmt.Errorf("custom watch source: %w", err)
}
if src == nil {
return fmt.Errorf("custom watch source must not be nil")
}
ctl.WithCustomWatch(src, mapper) Type guard
func isNilSource(s *controller.Source) bool {
return s == nil
} Prevention
- Make source constructors return errors instead of nil
- Call WithCustomWatch only after the source is fully built
- Add placeholder sources behind a not-implemented error, never a nil pointer
When it happens
Trigger: Calling WithCustomWatch(nil, mapper) — usually a Source variable that was never constructed, a source builder that returned nil on error, or scaffolding code with a placeholder nil before the source exists.
Common situations: Building custom sources in a later iteration and leaving nil placeholders; error-swallowing source constructors; conditional setup where one path forgets to build the source.
Related errors
- no indexer was supplied when creating a new cache Index
- reconciler must not be nil
- mapper must not be nil
- all indexers must have a non-empty name
- The Indexer must also implement one of the SingleIndexer or
AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15).
Data as JSON: /api/errors/cffbbee95745daca.
Report an issue: GitHub.