hibiken/asynq · warning

task already exists

Error message

task already exists

What it means

Internal errors.ErrDuplicateTask (internal/errors/errors.go) indicates another task with the same unique key holds the uniqueness lock, so enqueueing was rejected by the broker. It is the internal counterpart of the public asynq.ErrDuplicateTask, which the client produces by wrapping this value.

Solutions

  1. Catch the wrapped asynq.ErrDuplicateTask at the client layer and treat as a benign skip.
  2. Reduce uniqueTTL or wait for expiry before re-enqueueing.
  3. Vary the payload or add a nonce so the unique key differs.
  4. Clear the lock by deleting the unique key in Redis if you must enqueue immediately.

Example fix

// before
return client.Enqueue(task, asynq.Unique(ttl))
// after
_, err := client.Enqueue(task, asynq.Unique(ttl))
if errors.Is(err, asynq.ErrDuplicateTask) {
    return nil // already queued
}
return err
Defensive patterns

Strategy: type-guard

Validate before calling

// Client-side duplicate guard before hitting Redis:
if seen.Load(uniqueKey) { return ErrRecentlyEnqueued }

Type guard

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

Try / catch

err := enqueueWithUnique(task, ttl)
if isDuplicate(err) { return nil }
if err != nil { return err }

Prevention

When it happens

Trigger: Enqueueing with a Unique option while a task with the identical unique key (derived from type, payload, queue, and uniqueTTL) is still locked; used by rdb enqueue paths and surfaced through EnqueueContext/EnqueueUnique/AddToGroupUnique.

Common situations: Same as the public variant: fast periodic producers within TTL, duplicate retries after ambiguous network failures, and multiple producers racing on the same job.

Related errors


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

Appendix: source

Thrown at internal/errors/errors.go:173

	if !ok {
		return Unspecified
	}
	if e.Code == Unspecified {
		return CanonicalCode(e.Err)
	}
	return e.Code
}

/******************************************
    Domain Specific Error Types & Values
*******************************************/

var (
	// ErrNoProcessableTask indicates that there are no tasks ready to be processed.
	ErrNoProcessableTask = errors.New("no tasks are ready for processing")

	// ErrDuplicateTask indicates that another task with the same unique key holds the uniqueness lock.
	ErrDuplicateTask = errors.New("task already exists")

	// ErrTaskIdConflict indicates that another task with the same task ID already exist
	ErrTaskIdConflict = errors.New("task id conflicts with another task")
)

// TaskNotFoundError indicates that a task with the given ID does not exist
// in the given queue.
type TaskNotFoundError struct {
	Queue string // queue name
	ID    string // task id
}

func (e *TaskNotFoundError) Error() string {
	return fmt.Sprintf("cannot find task with id=%s in queue %q", e.ID, e.Queue)
}

// IsTaskNotFound reports whether any error in err's chain is of type TaskNotFoundError.
func IsTaskNotFound(err error) bool {

View on GitHub (pinned to d135f1439b)