hibiken/asynq · error · ErrHandlerNotFound
%w %q
Error message
%w %q
What it means
NotFound is the handler asynq uses when a task's type has no registered handler in a ServeMux. It wraps ErrHandlerNotFound and includes the task type so developers can see which type is unregistered. The server will record this error and the task goes through normal error/retry handling.
Solutions
- Register the missing type on the ServeMux: mux.HandleFunc(task.Type(), handler)
- Fix the type-name mismatch — use shared constants like "email:welcome" between producer and consumer
- If unhandled types are expected, mount mux.Use(asynq.NotFoundHandler()) behavior deliberately or log-and-ack via a custom middleware
Example fix
// before
mux.HandleFunc("email:welcome", welcomeHandler) // task type is "email:remind"
// after
mux.HandleFunc("email:welcome", welcomeHandler)
mux.HandleFunc("email:remind", remindHandler) Defensive patterns
Strategy: try-catch
Validate before calling
registered := map[string]bool{
"email:welcome": true,
"email:remind": true,
}
if !registered[t.Type()] {
log.Printf("warning: task type %q has no handler registered", t.Type())
} Type guard
func hasHandler(mux *asynq.ServeMux, taskType string) bool {
return knownTypes[taskType]
} Try / catch
if errors.Is(err, asynq.ErrHandlerNotFound) {
log.Printf("unregistered task type %q; dropping or routing to DLQ", taskType)
return nil // or forward for inspection
} Prevention
- Define all task type strings as shared constants imported by producer and consumer
- Add a startup test that enqueues one task of every type against the mux
- Keep producer and worker deployments in sync when adding new task types
When it happens
Trigger: Enqueuing/processing a task whose Type string was never registered via mux.HandleFunc/mux.Handle — typos in type names, task types added by newer producers but not by the consumer, or type strings built dynamically.
Common situations: Producer/consumer version skew where the producer enqueues a new task type the old worker doesn't know; refactoring renamed task constants on one side only; sharing type strings as raw literals instead of constants.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/e6f9f39173e45169.
Report an issue: GitHub.
Appendix: source
Thrown at servemux.go:156
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(context.Context, *Task) error) {
if handler == nil {
panic("asynq: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
// Use appends a MiddlewareFunc to the chain.
// Middlewares are executed in the order that they are applied to the ServeMux.
func (mux *ServeMux) Use(mws ...MiddlewareFunc) {
mux.mu.Lock()
defer mux.mu.Unlock()
mux.mws = append(mux.mws, mws...)
}
// NotFound returns an error indicating that the handler was not found for the given task.
func NotFound(ctx context.Context, task *Task) error {
return fmt.Errorf("%w %q", ErrHandlerNotFound, task.Type())
}
// NotFoundHandler returns a simple task handler that returns a “not found“ error.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
View on GitHub (pinned to d135f1439b)