go-redis/redis · error

redis: SUBSCRIBE is not allowed on pooled connections when c

Error message

redis: SUBSCRIBE is not allowed on pooled connections when client-side caching is enabled

What it means

Returned when a raw SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE is sent over an ordinary pooled connection on a CSC-enabled client. Client-side caching requires the pooled connection to stay in RESP3 tracking mode, but raw subscriptions put it into subscribe mode and break that contract. The typed pubsub methods (client.Subscribe) use a dedicated PubSub connection and remain allowed.

Source

Thrown at csc_integration.go:543

// errAuthWithCSC rejects runtime authentication because it can change one
// connection's ACL identity without changing the client's fixed cache namespace.
var errAuthWithCSC = errors.New(
	"redis: AUTH is not allowed when client-side caching is enabled")

// errHelloWithCSC rejects HELLO with arguments because it can switch a tracked
// connection out of RESP3 (and can also change authentication).
var errHelloWithCSC = errors.New(
	"redis: HELLO with arguments is not allowed when client-side caching is enabled")

// errResetWithCSC rejects RESET because it disables tracking and switches the
// connection to RESP2.
var errResetWithCSC = errors.New(
	"redis: RESET is not allowed when client-side caching is enabled")

// errSubscribeWithCSC rejects raw subscriptions on the ordinary pool. The
// typed Subscribe methods use dedicated PubSub connections and remain allowed.
var errSubscribeWithCSC = errors.New(
	"redis: SUBSCRIBE is not allowed on pooled connections when client-side caching is enabled")

// cscCommandError rejects commands that can make a pooled connection's state
// diverge from the assumptions used by CSC.
func (c *baseClient) cscCommandError(cmd Cmder) error {
	// The successful attachment signal is shared with derived clients.
	// initConn's internal command wrapper is exempt during library setup.
	if !c.cscTrackingRequested() || c.allowClientTracking {
		return nil
	}
	switch {
	case isClientTrackingCmd(cmd):
		return errClientTrackingWithCSC
	case isSelectCmd(cmd):
		return errSelectWithCSC
	case isAuthCmd(cmd):
		return errAuthWithCSC
	case isProtocolChangingHelloCmd(cmd):

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Use the typed pubsub API: pubsub := client.Subscribe(ctx, "channel").
  2. If raw subscribe is mandatory, disable built-in CSC (leave Options.ClientSideCache nil).

Example fix

// before
client.Do(ctx, "SUBSCRIBE", "events")
// after
pubsub := client.Subscribe(ctx, "events")
msg, err := pubsub.ReceiveMessage(ctx)
Defensive patterns

Strategy: validation

Validate before calling

// Always use the typed pubsub API instead of raw SUBSCRIBE.
o := client.Options()
if o.ClientSideCache != nil || o.ClientSideCacheConfig != nil {
    pubsub := client.Subscribe(ctx, "channel")
    _ = pubsub
} else {
    client.Do(ctx, "SUBSCRIBE", "channel")
}

Type guard

func canRawSubscribe(c *redis.Client) bool {
    o := c.Options()
    return o.ClientSideCache == nil && o.ClientSideCacheConfig == nil
}

Try / catch

err := client.Do(ctx, "SUBSCRIBE", "ch").Err()
if err != nil && strings.Contains(err.Error(), "SUBSCRIBE is not allowed") {
    pubsub := client.Subscribe(ctx, "ch")
    _ = pubsub.ReceiveMessage(ctx)
}

Prevention

When it happens

Trigger: Calling client.Do(ctx, "SUBSCRIBE", "channel") on a client with Options.ClientSideCache set. The cscCommandError guard fires via isSubscribeCmd for any subscribe-family command sent through the normal process path.

Common situations: Mixing a hand-rolled subscribe loop with a CSC-enabled client, or copy-pasting subscribe code that uses Do() instead of the typed API.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/b672ae3e333e53ea.json. Report an issue: GitHub.