hibiken/asynq · warning

task already exists

Error message

task already exists

What it means

ErrDuplicateTask (errors.ErrDuplicateTask in package errors, wrapped as asynq.ErrDuplicateTask in client.go) indicates the task could not be enqueued because a task with the same uniqueness key already exists. It only applies to tasks enqueued with a Unique option (Unique/uniqueTTL). The client translates the internal errors.ErrDuplicateTask from the broker into the public sentinel via errors.Is.

Solutions

  1. Check errors.Is(err, asynq.ErrDuplicateTask) and treat it as success/expected when the task is already queued.
  2. Shorten the Unique TTL so locks expire sooner, or clear the uniqueness lock by waiting for TTL expiry.
  3. Include a nonce or ID in the task payload to differentiate genuinely distinct tasks.
  4. Use TaskID option instead if you want conflict semantics rather than uniqueness semantics.

Example fix

// before
_, err := client.Enqueue(task, asynq.Unique(24*time.Hour))
if err != nil { log.Fatal(err) }
// after
_, err := client.Enqueue(task, asynq.Unique(24*time.Hour))
if errors.Is(err, asynq.ErrDuplicateTask) {
    log.Println("task already enqueued; skipping")
    return
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Nothing to pre-validate (uniqueness is server-side); optionally track TTLs locally:
if time.Since(lastEnqueued[key]) < ttl { return ErrAlreadyQueued }

Type guard

func isDuplicateTask(err error) bool { return errors.Is(err, asynq.ErrDuplicateTask) }

Try / catch

if _, err := client.Enqueue(task, asynq.Unique(ttl)); err != nil {
    if isDuplicateTask(err) { return nil }
    return fmt.Errorf("enqueue: %w", err)
}

Prevention

When it happens

Trigger: Calling client.Enqueue with asynq.Unique(ttl) (or EnqueueUnique/AddToGroupUnique) while an identical task (same type, payload, queue, and unique key) is still within its uniqueness TTL; a prior identical enqueue has not expired yet.

Common situations: Periodic job schedulers firing faster than the Unique TTL; retrying an enqueue after a timeout when the first attempt actually succeeded and set the unique lock; multiple workers/producers racing to enqueue the same deduplicated job.

Related errors


AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07). Data as JSON: /api/errors/b4d286ee48af0a00. Report an issue: GitHub.

Appendix: source

Thrown at client.go:248

// Alternatively, NewTaskWithHeaders can be used to create a task with headers
// directly, which may be preferable when headers are an intrinsic part of the
// task definition rather than enqueue-time configuration.
func Header(key, value string) Option {
	return headerOption{key, value}
}

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
}

View on GitHub (pinned to d135f1439b)