hibiken/asynq · critical

asynq: unsupported RedisConnOpt type %T

Error message

asynq: unsupported RedisConnOpt type %T

What it means

asynq.NewClient panics when the provided RedisConnOpt's MakeRedisClient() method does not return a value implementing redis.UniversalClient (go-redis). NewClient only accepts connection options whose underlying client is a go-redis UniversalClient; any custom or foreign RedisConnOpt implementation triggers this panic immediately at construction time.

Solutions

  1. Use one of the built-in options: asynq.RedisClientOpt, RedisClusterClientOpt, or RedisFailoverClientOpt
  2. If using a custom RedisConnOpt, make MakeRedisClient() return a *redis.Client / *redis.ClusterClient / *redis.UniversalClient that satisfies redis.UniversalClient
  3. If you already hold a go-redis client, use asynq.NewClientFromRedisClient instead of NewClient
  4. Check the concrete type returned by MakeRedisClient with %T and confirm it implements redis.UniversalClient

Example fix

// before
conn := myCustomRedisWrapper{addr: ":6379"} // MakeRedisClient returns the wrapper
client := asynq.NewClient(conn)

// after
client := asynq.NewClient(asynq.RedisClientOpt{Addr: ":6379"})
// or, with an existing go-redis client:
client := asynq.NewClientFromRedisClient(rdb)
Defensive patterns

Strategy: validation

Validate before calling

opt := asynq.RedisClientOpt{Addr: ":6379"}
if _, ok := opt.MakeRedisClient().(redis.UniversalClient); !ok {
    log.Fatal("RedisConnOpt does not produce a go-redis UniversalClient")
}
client := asynq.NewClient(opt)

Type guard

func isValidRedisConnOpt(r asynq.RedisConnOpt) bool {
    _, ok := r.MakeRedisClient().(redis.UniversalClient)
    return ok
}

Prevention

When it happens

Trigger: Passing a RedisConnOpt whose MakeRedisClient() returns nil, a raw *redis.Client typed as something else, or a fully custom type that does not implement the interface; constructing asynq.NewClient with a hand-rolled adapter struct; passing a zero-value custom conn option.

Common situations: Wrapping a redis cluster/sentinel setup in a custom config struct instead of asynq.RedisClientOpt/RedisClusterClientOpt/RedisFailoverClientOpt; upgrading go-redis versions so the UniversalClient interface no longer matches; typos where a config object that merely resembles RedisConnOpt is passed in.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at client.go:39

// A Client is responsible for scheduling tasks.
//
// A Client is used to register tasks that should be processed
// immediately or some time in the future.
//
// Clients are safe for concurrent use by multiple goroutines.
type Client struct {
	broker base.Broker
	// When a Client has been created with an existing Redis connection, we do
	// not want to close it.
	sharedConnection bool
}

// NewClient returns a new Client instance given a redis connection option.
func NewClient(r RedisConnOpt) *Client {
	redisClient, ok := r.MakeRedisClient().(redis.UniversalClient)
	if !ok {
		panic(fmt.Sprintf("asynq: unsupported RedisConnOpt type %T", r))
	}
	client := NewClientFromRedisClient(redisClient)
	client.sharedConnection = false
	return client
}

// NewClientFromRedisClient returns a new instance of Client given a redis.UniversalClient
// Warning: The underlying redis connection pool will not be closed by Asynq, you are responsible for closing it.
func NewClientFromRedisClient(c redis.UniversalClient) *Client {
	return &Client{broker: rdb.NewRDB(c), sharedConnection: true}
}

type OptionType int

const (
	MaxRetryOpt OptionType = iota
	QueueOpt
	TimeoutOpt

View on GitHub (pinned to d135f1439b)