hashicorp/consul · error
mapper must not be nil
Error message
mapper must not be nil
What it means
Controller.WithCustomWatch requires both a Source and a CustomDependencyMapper. A nil mapper panics at setup: events from the custom source would arrive with no way to map them onto the managed resource type, so the controller could never enqueue work — the constructor catches this misuse up front.
Source
Thrown at internal/controller/controller.go:147
// 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 ctl
}
// WithBackoff changes the base and maximum backoff values for the controller's
// retry rate limiter.View on GitHub (pinned to 2397ff0d76)
Solutions
- Implement and pass a CustomDependencyMapper before starting the controller
- If mapping is conditional, still provide a mapper that filters and returns nothing rather than nil
- Guard construction: if mapper == nil { return errors.New("mapper required") }
Example fix
// before
ctl.WithCustomWatch(src, nil) // panic: mapper must not be nil
// after
type mapper struct{}
func (mapper) Map(watched, rt *pbresource.Resource) ([]reconcile.Request, error) { ... }
ctl.WithCustomWatch(src, mapper{}) Defensive patterns
Strategy: validation
Validate before calling
// validate the mapper before wiring
if mapper == nil {
return fmt.Errorf("custom watch requires a non-nil DependencyMapper")
}
ctl.WithCustomWatch(src, mapper) Type guard
func isNilMapper(m controller.CustomDependencyMapper) bool {
if m == nil {
return true
}
v := reflect.ValueOf(m)
return v.Kind() == reflect.Ptr && v.IsNil()
} Prevention
- Write the mapper before registering the watch; no placeholder nils
- For conditional mapping, provide a mapper that filters and returns empty requests
- Smoke-test controller construction so missing mappers fail at build time
When it happens
Trigger: Calling WithCustomWatch(src, nil) — a mapper variable declared but never assigned, a conditional branch that skips mapper construction, or scaffolding code where the mapper is still to be written.
Common situations: Incrementally building custom watches and leaving the mapper for later; refactoring mappers into helpers that can return nil; feature-gated mapper construction.
Related errors
- no indexer was supplied when creating a new cache Index
- reconciler must not be nil
- source 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/2b1a11e442a8ccb7.
Report an issue: GitHub.