hibiken/asynq · error
batch enqueue does not support unique tasks
Error message
batch enqueue does not support unique tasks
What it means
BatchEnqueue does not support unique tasks — tasks enqueued with asynq.Unique(ttl) or asynq.TaskID retention producing a uniqueTTL. Unique-task bookkeeping (uniqueness locks/TTL keys) is incompatible with the batch enqueue pipeline, so asynq rejects any item whose options set uniqueTTL > 0. The error is recorded per-item in BatchEnqueueResult.Err.
Solutions
- Remove the asynq.Unique(...) option for tasks submitted via BatchEnqueue, or enqueue them individually with Client.Enqueue.
- Implement application-level deduplication (e.g. a Redis SETNX or DB unique column) instead of asynq uniqueness when batching.
- Separate unique tasks from the batch and submit them in a second loop via Enqueue.
- Inspect BatchEnqueueResult.Err for each item and re-enqueue unique tasks individually.
Example fix
// before
opt := []asynq.Option{asynq.Queue("sync"), asynq.Unique(10 * time.Minute)}
client.BatchEnqueue(ctx, task) // rejected
// after
if opts.uniqueTTL > 0 {
_, err := client.Enqueue(task, opts...)
} else {
client.BatchEnqueue(ctx, task)
} Defensive patterns
Strategy: validation
Validate before calling
func batchableUnique(opts []asynq.Option, ttl time.Duration) bool {
if ttl > 0 { return false }
return true
}
// only pass asynq.Unique to single Enqueue calls Type guard
func hasNoUniqueTTL(ttl time.Duration) bool { return ttl <= 0 } Try / catch
for i, r := range results {
if r.Err != nil && strings.Contains(r.Err.Error(), "does not support unique tasks") {
// re-enqueue individually with asynq.Unique
}
} Prevention
- Route unique tasks through Client.Enqueue, never BatchEnqueue.
- Use application-level dedup keys when batching.
- Keep uniqueness options out of shared option-slice helpers.
- Check BatchEnqueueResult.Err per item.
When it happens
Trigger: Calling Client.BatchEnqueue with a task whose options include asynq.Unique(duration), e.g. deduplicating scheduled sync tasks that were also submitted in a batch.
Common situations: Reusing option sets across Enqueue and BatchEnqueue code paths; enabling uniqueness via a shared config helper; migrating a unique task from single enqueue into a batch submission.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- batch enqueue does not support group tasks
- redis connection is shared so the Inspector can't be closed…
- asynq
- asynq: unsupported log level
- asynq: server cannot run with nil handler
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/594f7cb76c58f58d.
Report an issue: GitHub.
Appendix: source
Thrown at client.go:533
results[i] = BatchEnqueueResult{Err: fmt.Errorf("task cannot be nil")}
continue
}
if strings.TrimSpace(task.Type()) == "" {
results[i] = BatchEnqueueResult{Err: fmt.Errorf("task typename cannot be empty")}
continue
}
merged := append(task.opts, opts...)
opt, err := composeOptions(merged...)
if err != nil {
results[i] = BatchEnqueueResult{Err: err}
continue
}
if opt.group != "" {
results[i] = BatchEnqueueResult{Err: fmt.Errorf("batch enqueue does not support group tasks")}
continue
}
if opt.uniqueTTL > 0 {
results[i] = BatchEnqueueResult{Err: fmt.Errorf("batch enqueue does not support unique tasks")}
continue
}
deadline := noDeadline
if !opt.deadline.IsZero() {
deadline = opt.deadline
}
timeout := noTimeout
if opt.timeout != 0 {
timeout = opt.timeout
}
if deadline.Equal(noDeadline) && timeout == noTimeout {
timeout = defaultTimeout
}
msg := &base.TaskMessage{
ID: opt.taskID,
Type: task.Type(),
Payload: task.Payload(),
Headers: task.Headers(),View on GitHub (pinned to d135f1439b)