redis/redis-py · warning · SlotNotCoveredError
shard channel(s) left unreconciled; slot(s) not covered by…
Error message
{len(uncovered)} shard channel(s) left unreconciled; slot(s) not covered by the cluster: {uncovered!r} What it means
Raised as a SlotNotCoveredError during shard-channel (SSUBSCRIBE) reconciliation when one or more shard channels could not be migrated to their new owning node because get_node_from_key() raised SlotNotCoveredError for those channels' slots. The reconciliation loop (cluster.py:3287-3341) defers channels whose slots are transiently uncovered, continues reconciling siblings, and then surfaces the unresolved list so the caller/logs know reconciliation was incomplete. Retry happens on the next slots-cache change notification.
Solutions
- Treat this as transient: the client retries reconciliation on the next topology change. Ensure topology refresh is enabled and reinitialize_steps > 0.
- Verify the cluster eventually covers all relevant slots (CLUSTER NODES) and no slots are permanently unassigned.
- If persistent, trigger a manual topology refresh (client.cluster_reload_slots()) and allow the next reconciliation pass to retry.
- Log the uncovered channels from the error message to identify which subscriptions need attention.
Example fix
null
Defensive patterns
Strategy: retry
Validate before calling
# Before relying on shard subscriptions, verify slot coverage for channels
from redis.cluster import key_slot
for channel in my_shard_channels:
slot = key_slot(channel.encode()) % 16384
if not client.nodes_manager.slots_cache.get(slot):
client.cluster_reload_slots()
break Type guard
def shard_channels_covered(client, channels: list) -> bool:
from redis.cluster import key_slot
for ch in channels:
slot = key_slot(ch.encode() if isinstance(ch, str) else ch) % 16384
if not client.nodes_manager.slots_cache.get(slot):
return False
return True Try / catch
from redis.exceptions import SlotNotCoveredError
import time
for attempt in range(3):
try:
# trigger reconciliation or the operation that surfaces it
break
except SlotNotCoveredError as e:
if 'unreconciled' in str(e):
client.cluster_reload_slots()
time.sleep(1)
else:
raise Prevention
- Treat shard-channel reconciliation errors as transient; the client retries on the next topology notification.
- Ensure reinitialize_steps > 0 so topology refresh runs on slot errors.
- Verify cluster health and full slot coverage when these errors persist.
When it happens
Trigger: Subscribing to shard channels via SSUBSCRIBE, then a cluster topology change (failover, CLUSTER SETSLOT) occurs that moves some channels' slots to nodes the client hasn't discovered yet. The reconciliation pass cannot find an owner for those slots.
Common situations: Cluster failover or resharding while shard-channel subscriptions are active, causing slot ownership to shift faster than topology refresh can track.
Related errors
- Slot " " is not covered by the cluster.
- Slot " " not covered by the cluster. "require_full_coverage=
- A non health check response was cleaned by execute_command
- All slots are not covered after query all startup_nodes.
- Buffer is closed.
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/471d16fc9519f7e8.
Report an issue: GitHub.
Appendix: source
Thrown at redis/cluster.py:3338
e,
)
if first_migrate_error is None:
first_migrate_error = e
continue
# Garbage-collect per-node pubsubs that no longer hold any
# subscription so their connections are released.
for name, pubsub in list(self.node_pubsub_mapping.items()):
if not pubsub.subscribed:
try:
pubsub.reset()
except Exception:
pass
self.node_pubsub_mapping.pop(name, None)
if uncovered:
# Surface the uncovered channels so the caller (and observer
# notification path) knows reconciliation was incomplete. All
# coverable siblings have already been migrated above.
raise SlotNotCoveredError(
f"{len(uncovered)} shard channel(s) left unreconciled; "
f"slot(s) not covered by the cluster: {uncovered!r}"
)
if first_migrate_error is not None and not made_progress:
# Every migration attempted in this pass failed transiently and
# nothing else made progress. Re-raise the first caught error
# (typically the root cause; later failures are often downstream
# symptoms of the same unreachable node) so the worker's done-
# callback surfaces a single representative failure through the
# same logger channel used for SlotNotCoveredError. Per-channel
# WARNINGs above preserve the full forensic detail.
raise first_migrate_error
def _migrate_shard_channel(self, channel, handler, old_name, new_node):
# Detach from the old per-node pubsub, best-effort: the old node may
# already be unreachable during migration / failover.
if old_name and old_name in self.node_pubsub_mapping:
old_pubsub = self.node_pubsub_mapping[old_name]View on GitHub (pinned to 6a6b581b48)