hibiken/asynq · error

group key cannot be empty

Error message

group key cannot be empty

What it means

Validation guard in composeOptions: the WithGroup option was constructed with a blank (empty or whitespace-only) group key. Aggregation requires a non-empty group key to route the task into a group, so enqueueing fails immediately instead of creating an invalid group.

Solutions

  1. Validate the group key is non-blank before enqueueing.
  2. Fall back to enqueueing without the Group option, or use a default key.
  3. Fix the upstream data source producing the empty key.

Example fix

// before
client.Enqueue(task, asynq.Group(tenant.Name))
// after
if strings.TrimSpace(tenant.Name) == "" {
    return errors.New("cannot enqueue: empty group key")
}
client.Enqueue(task, asynq.Group(tenant.Name))
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(groupKey) == "" { return errors.New("group key must be non-empty") }

Type guard

func validGroupKey(k string) bool { return strings.TrimSpace(k) != "" }

Try / catch

// Prefer validation; on hit, fall back to non-grouped enqueue:
if strings.Contains(err.Error(), "group key cannot be empty") { client.Enqueue(task) }

Prevention

When it happens

Trigger: Calling client.Enqueue(task, asynq.Group("")) or Group(" "), typically when the group key derives from an empty variable (tenant ID, event name).

Common situations: Aggregation key sourced from an unset request field; template/config producing empty keys; forgetting that group keys must also satisfy the aggregator's GroupMaxDelay/size settings.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at client.go:317

			res.timeout = time.Duration(opt)
		case deadlineOption:
			res.deadline = time.Time(opt)
		case uniqueOption:
			ttl := time.Duration(opt)
			if ttl < 1*time.Second {
				return option{}, errors.New("Unique TTL cannot be less than 1s")
			}
			res.uniqueTTL = ttl
		case processAtOption:
			res.processAt = time.Time(opt)
		case processInOption:
			res.processAt = time.Now().Add(time.Duration(opt))
		case retentionOption:
			res.retention = time.Duration(opt)
		case groupOption:
			key := string(opt)
			if isBlank(key) {
				return option{}, errors.New("group key cannot be empty")
			}
			res.group = key
		case headerOption:
			key, value := opt[0], opt[1]
			res.headers[key] = value
		default:
			// ignore unexpected option
		}
	}
	return res, nil
}

// isBlank returns true if the given s is empty or consist of all whitespaces.
func isBlank(s string) bool {
	return strings.TrimSpace(s) == ""
}

const (

View on GitHub (pinned to d135f1439b)