hibiken/asynq · critical
asynq: multiple registrations for
Error message
asynq: multiple registrations for %s
What it means
ServeMux.Handle panics with "asynq: multiple registrations for <pattern>" when a handler was already registered for the same task pattern. Unlike net/http's mux, asynq's ServeMux does not allow overwriting; each task-type pattern may be registered exactly once, and duplicate registration is a programming error.
Solutions
- Remove the duplicate registration or make patterns unique per task type
- Centralize handler registration in one function invoked exactly once
- Deduplicate config-driven pattern lists before the registration loop
- If overwriting was intended, keep a map[string]asynq.Handler yourself and register once with the final handler
Example fix
// before
mux.Handle("email:welcome", newWelcomeHandler(cfg))
mux.Handle("email:welcome", newWelcomeHandler(cfgV2)) // panics
// after
mux.Handle("email:welcome", newWelcomeHandler(cfgV2)) // single registration only Defensive patterns
Strategy: validation
Validate before calling
registered := map[string]bool{}
func registerOnce(mux *asynq.ServeMux, pattern string, h asynq.Handler) {
if registered[pattern] {
return // or log.Fatalf("duplicate registration for %s", pattern)
}
registered[pattern] = true
mux.Handle(pattern, h)
} Prevention
- Centralize all mux.Handle calls in a single setup function executed once
- Ensure task-type constants are unique (greet with linters or a registry test)
- Deduplicate config-driven pattern lists before registration
- Be careful with init() plus explicit setup both registering the same patterns
When it happens
Trigger: Calling mux.Handle("email:welcome", h1) then mux.Handle("email:welcome", h2); running registration code twice (e.g. both in an init and a setup function, or on hot-reload); registering the same handler for two identical constant patterns in a loop over config.
Common situations: Duplicate task-type constants in different files; duplicated setup calls during tests or server restart in-process; config listing the same task type twice.
Understand the failure class
Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.
Related errors
- asynq: invalid pattern
- asynq: nil handler
- asynq: unsupported RedisConnOpt type %T
- inspeq: unsupported RedisConnOpt type %T
- asynq: cannot start uninitialized PeriodicTaskManager; use…
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/d4b880778c50c5be.
Report an issue: GitHub.
Appendix: source
Thrown at servemux.go:112
}
return nil, ""
}
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if strings.TrimSpace(pattern) == "" {
panic("asynq: invalid pattern")
}
if handler == nil {
panic("asynq: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("asynq: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
mux.es = appendSorted(mux.es, e)
}
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
n := len(es)
i := sort.Search(n, func(i int) bool {
return len(es[i].pattern) < len(e.pattern)
})
if i == n {
return append(es, e)
}View on GitHub (pinned to d135f1439b)