Skip to content

Cluster error handling raises AttributeError: 'ClusterNode' object has no attribute 'db' after connection timeout #3997

@eg-camarasoft

Description

@eg-camarasoft

Description

When a Redis Cluster node connection fails (timeout or server-closed connection), redis-py attempts to emit the after_command_execution event. During this process it assumes the connection object has a .db attribute.

However, in some error paths the object passed is a ClusterNode rather than a Connection. This results in a secondary exception:

AttributeError: 'ClusterNode' object has no attribute 'db'

This masks the original Redis connectivity error and causes application code that correctly catches RedisError to still crash.


Observed behavior

A real connection error occurs first:

redis.exceptions.ConnectionError: Connection closed by server.
...
redis.exceptions.TimeoutError: Timeout connecting to server

But redis-py then throws:

AttributeError: 'ClusterNode' object has no attribute 'db'

Traceback excerpt:

File ".../redis/cluster.py", line 1755, in _emit_after_command_execution_event
    db_namespace=str(connection.db),
AttributeError: 'ClusterNode' object has no attribute 'db'

Because this is not a RedisError, it escapes application error handling.


Expected behavior

If Redis cluster connectivity fails, redis-py should raise a RedisError subclass (ConnectionError, TimeoutError, etc.) and not emit secondary exceptions from observability/event hooks.

Event emission should handle missing attributes safely.


Versions

Working:

redis-py 7.1.0

Broken:

redis-py 7.2.1

Python:

Python 3.13

Environment

  • Redis Cluster
  • Django application
  • redis-py RedisCluster
  • cluster nodes configured via startup_nodes

Example initialization:

from redis.cluster import RedisCluster, ClusterNode

nodes = [
    ClusterNode("host1", 6379),
    ClusterNode("host2", 6379),
]

client = RedisCluster(
    startup_nodes=nodes,
    username="...",
    password="...",
    socket_connect_timeout=5,
    socket_timeout=3,
)

Minimal failing example

The issue occurs during a normal command when the cluster node cannot be reached:

client.get("test-key")

If the node is unreachable or times out, the above command may produce:

AttributeError: 'ClusterNode' object has no attribute 'db'

instead of a Redis exception.


Workaround

Application code must currently catch AttributeError in addition to RedisError:

try:
    client.get("key")
except (RedisError, AttributeError):
    ...

This is undesirable because AttributeError should not be part of the public API surface.


Suspected cause

The cluster error path passes a ClusterNode object as connection to _emit_after_command_execution_event, which assumes a Connection object and accesses connection.db.

Example location in traceback:

_emit_after_command_execution_event(...)
db_namespace=str(connection.db)

ClusterNode does not expose .db.


Impact

  • Masks the original Redis connection failure
  • Breaks error handling that correctly catches RedisError
  • Can crash production services on transient cluster connectivity issues

Suggested fix

Guard access to connection.db in _emit_after_command_execution_event, for example:

db_namespace = getattr(connection, "db", None)

or ensure only Connection objects are passed to the event emitter.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions