redis/redis-rb · error · Redis::SubscriptionError

This client is not subscribed

Error message

This client is not subscribed

What it means

The no-block forms of unsubscribe/punsubscribe (and subscribe-family calls without a block) send the command over @subscription_client — the dedicated pub/sub socket created by a previous subscribe-with-block call. If no subscription is currently active on this instance, @subscription_client is nil and Redis raises SubscriptionError 'This client is not subscribed'.

Source

Thrown at lib/redis.rb:321

      end

      begin
        # The pub/sub second socket is opened via @client.pubsub, which connects through
        # ensure_connected rather than a command path. Route it through #synchronize so the same
        # RESP3->RESP2 fallback applies when subscribe is the first operation against an old server.
        @subscription_client = SubscribedClient.new(synchronize(&:pubsub))
        if timeout > 0
          @subscription_client.send(method, timeout, *channels, &block)
        else
          @subscription_client.send(method, *channels, &block)
        end
      ensure
        @subscription_client&.close
        @subscription_client = nil
      end
    else
      unless @subscription_client
        raise SubscriptionError, "This client is not subscribed"
      end

      @subscription_client.call_v([method].concat(channels))
    end
  end
end

require "redis/version"
require "redis/lib_identity"
require "redis/client"
require "redis/pipeline"
require "redis/subscribe"

View on GitHub (pinned to 2ba9010b91)

Solutions

  1. Drop the defensive unsubscribe: exiting the subscribe block already closes the subscription socket (@subscription_client&.close runs in ensure)
  2. Send unsubscribe from inside the subscription block, where @subscription_client is guaranteed live
  3. Guard the call with your own subscription-liveness flag before invoking the no-block form

Example fix

# before
redis.subscribe('news') { |on| on.message { |_, m| process(m) } }
redis.unsubscribe('news')  # raises: block exit already closed the subscription

# after
redis.subscribe('news') do |on|
  on.message { |_, m| process(m) }
  # to stop early, unsubscribe from in here while the subscription is live:
  # redis.unsubscribe('news')
end
Defensive patterns

Strategy: validation

Validate before calling

def stop_subscription(redis, *channels)
  redis.unsubscribe(*channels) if @subscribing  # app-side flag set around the subscribe block
end

Try / catch

begin
  redis.unsubscribe('chan')
rescue Redis::SubscriptionError
  # already unsubscribed / subscription socket closed — nothing to do
end

Prevention

When it happens

Trigger: redis.unsubscribe('chan') or redis.punsubscribe('pattern*') called outside a live subscription block: before any subscribe, after the subscribe block already returned (its ensure closes the socket), or on a fresh client that never subscribed.

Common situations: Defensive cleanup code calling unsubscribe in an ensure block after the subscription loop already exited; shutting down a consumer that never started; a subscription that ended via subscribe_with_timeout and the teardown path still tries to unsubscribe.

Related errors


AI-assisted analysis of redis/redis-rb@2ba9010b91 (2026-08-23). Data as JSON: /api/errors/54a35f93a71bf530. Report an issue: GitHub.