hibiken/asynq · error
task ID conflicts with another task
Error message
task ID conflicts with another task
What it means
ErrTaskIDConflict indicates the task could not be enqueued because another task with the same TaskID already exists (in active/pending/scheduled state). It only applies to tasks enqueued with a TaskID option. The client wraps the internal errors.ErrTaskIdConflict into this public sentinel.
Solutions
- Check errors.Is(err, asynq.ErrTaskIDConflict) and skip/requeue later — the task already exists.
- Use a unique ID (UUID) per task instead of reusing stable business IDs.
- Wait for the existing task to finish and its retention period to expire before reusing the ID.
- Shorten the Retention duration on the original task so the ID frees up sooner.
Example fix
// before
_, err := client.Enqueue(task, asynq.TaskID(userID))
// after
_, err := client.Enqueue(task, asynq.TaskID(fmt.Sprintf("sync:%s:%d", userID, time.Now().Unix()))) Defensive patterns
Strategy: try-catch
Validate before calling
// Check whether an ID-derived task may still exist before enqueue:
if activeTaskIDs[taskID] { return ErrTaskInProgress } Type guard
func isTaskIDConflict(err error) bool { return errors.Is(err, asynq.ErrTaskIDConflict) } Try / catch
if _, err := client.Enqueue(task, asynq.TaskID(id)); err != nil {
if isTaskIDConflict(err) { return ErrAlreadySubmitted }
return err
} Prevention
- Use UUIDs or include a monotonic sequence in TaskID
- Set Retention deliberately — a task ID is blocked until retention expires
- Document TaskID semantics for anyone reusing business keys as IDs
When it happens
Trigger: Calling client.Enqueue with asynq.TaskID(id) where a task with that exact ID still exists in any queue state (pending, active, scheduled, retry, archived, or within retention).
Common situations: Using user/entity IDs as TaskID without allowing for task completion time; re-enqueuing after retention-expiry assumptions are wrong; two producers picking the same deterministic TaskID.
Related errors
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/b7b6f0e31c4c5bf2.
Report an issue: GitHub.
Appendix: source
Thrown at client.go:253
}
func (h headerOption) String() string {
var bytes []byte
bytes, _ = json.Marshal(h)
return fmt.Sprintf("Header(%s)", bytes)
}
func (h headerOption) Type() OptionType { return HeaderOpt }
func (h headerOption) Value() interface{} { return [2]string{h[0], h[1]} }
// ErrDuplicateTask indicates that the given task could not be enqueued since it's a duplicate of another task.
//
// ErrDuplicateTask error only applies to tasks enqueued with a Unique option.
var ErrDuplicateTask = errors.New("task already exists")
// ErrTaskIDConflict indicates that the given task could not be enqueued since its task ID already exists.
//
// ErrTaskIDConflict error only applies to tasks enqueued with a TaskID option.
var ErrTaskIDConflict = errors.New("task ID conflicts with another task")
type option struct {
retry int
queue string
taskID string
timeout time.Duration
deadline time.Time
uniqueTTL time.Duration
processAt time.Time
retention time.Duration
group string
headers map[string]string
}
// composeOptions merges user provided options into the default options
// and returns the composed option.
// It also validates the user provided options and returns an error if any of
// the user provided options fail the validations.View on GitHub (pinned to d135f1439b)