hibiken/asynq · critical
asynq: invalid pattern
Error message
asynq: invalid pattern
What it means
ServeMux.Handle panics with "asynq: invalid pattern" when the task pattern string is empty or consists only of whitespace. Every registered handler must have a non-empty pattern identifying the task type it handles, so registering with "" (or " ") is treated as a programming error and panics rather than returning an error.
Solutions
- Pass a non-empty task type string, e.g. mux.Handle("email:welcome", handler)
- Validate config/env values supplying task type names before registering
- Add a startup guard that fails fast if any configured task type is empty
- Check constants used as patterns for accidental empty values
Example fix
// before
mux.Handle(cfg.TaskType, handler) // cfg.TaskType == ""
// after
if strings.TrimSpace(cfg.TaskType) == "" {
log.Fatal("task type must be configured")
}
mux.Handle(cfg.TaskType, handler) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(pattern) == "" {
return errors.New("task pattern must be a non-empty string")
}
mux.Handle(pattern, handler) Type guard
func validPattern(p string) bool { return strings.TrimSpace(p) != "" } Try / catch
defer func() {
if r := recover(); r != nil {
log.Fatalf("mux registration failed: %v", r)
}
}()
mux.Handle(pattern, handler) Prevention
- Define task-type names as constants, never build them from possibly-empty config without validation
- Validate config/env-sourced task names at startup
- Search for mux.Handle calls with non-literal patterns and audit them
- Fail fast in an init test registering all expected patterns
When it happens
Trigger: Calling mux.Handle("", handler) or mux.HandleFunc(" ", fn); building the pattern dynamically from an empty config value or environment variable; a task-type constant that was left uninitialized (empty string).
Common situations: Task type names read from config/flags that default to empty string; string concatenation producing an empty prefix; refactoring that removed the literal task type name from the Handle call.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- asynq: nil handler
- asynq: multiple registrations for
- rate.NewSemaphore: maxTokens cannot be less than 1
- rate.NewSemaphore: scope should not be empty
- asynq: unsupported RedisConnOpt type %T
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/2a0b57d7d72254f6.
Report an issue: GitHub.
Appendix: source
Thrown at servemux.go:106
// Check for longest valid match.
// mux.es contains all patterns from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(typename, e.pattern) {
return e.h, e.pattern
}
}
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)View on GitHub (pinned to d135f1439b)