{"record":{"id":"54a35f93a71bf530","repo":"redis/redis-rb","slug":"this-client-is-not-subscribed","errorCode":null,"errorMessage":"This client is not subscribed","messagePattern":"This client is not subscribed","errorType":"exception","errorClass":"Redis::SubscriptionError","httpStatus":null,"severity":"error","filePath":"lib/redis.rb","lineNumber":321,"sourceCode":"      end\n\n      begin\n        # The pub/sub second socket is opened via @client.pubsub, which connects through\n        # ensure_connected rather than a command path. Route it through #synchronize so the same\n        # RESP3->RESP2 fallback applies when subscribe is the first operation against an old server.\n        @subscription_client = SubscribedClient.new(synchronize(&:pubsub))\n        if timeout > 0\n          @subscription_client.send(method, timeout, *channels, &block)\n        else\n          @subscription_client.send(method, *channels, &block)\n        end\n      ensure\n        @subscription_client&.close\n        @subscription_client = nil\n      end\n    else\n      unless @subscription_client\n        raise SubscriptionError, \"This client is not subscribed\"\n      end\n\n      @subscription_client.call_v([method].concat(channels))\n    end\n  end\nend\n\nrequire \"redis/version\"\nrequire \"redis/lib_identity\"\nrequire \"redis/client\"\nrequire \"redis/pipeline\"\nrequire \"redis/subscribe\"\n","sourceCodeStart":303,"sourceCodeEnd":334,"githubUrl":"https://github.com/redis/redis-rb/blob/2ba9010b91dab9e0fde1fbae3a9aae003f8bc307/lib/redis.rb#L303-L334","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Drop the defensive unsubscribe: exiting the subscribe block already closes the subscription socket (@subscription_client&.close runs in ensure)","Send unsubscribe from inside the subscription block, where @subscription_client is guaranteed live","Guard the call with your own subscription-liveness flag before invoking the no-block form"],"exampleFix":"# before\nredis.subscribe('news') { |on| on.message { |_, m| process(m) } }\nredis.unsubscribe('news')  # raises: block exit already closed the subscription\n\n# after\nredis.subscribe('news') do |on|\n  on.message { |_, m| process(m) }\n  # to stop early, unsubscribe from in here while the subscription is live:\n  # redis.unsubscribe('news')\nend","handlingStrategy":"validation","validationCode":"def stop_subscription(redis, *channels)\n  redis.unsubscribe(*channels) if @subscribing  # app-side flag set around the subscribe block\nend","typeGuard":null,"tryCatchPattern":"begin\n  redis.unsubscribe('chan')\nrescue Redis::SubscriptionError\n  # already unsubscribed / subscription socket closed — nothing to do\nend","preventionTips":["Send unsubscribe from inside the subscribe block, not after it returns","Rely on block exit to close the subscription socket — no manual cleanup needed","Track subscription liveness with an app-side flag around the block"],"tags":["pubsub","subscription","cleanup"],"backgroundTag":"pubsub-not-subscribed","analyzedSha":"2ba9010b91dab9e0fde1fbae3a9aae003f8bc307","analyzedAt":"2026-08-23T03:54:57.017Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}