{"record":{"id":"4f2d6b101093029a","repo":"redis/redis-py","slug":"cannot-create-cache-key","errorCode":null,"errorMessage":"Cannot create cache key.","messagePattern":"Cannot create cache key\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"redis/connection.py","lineNumber":1780,"sourceCode":"        # next read_response does not try to cache their reply under a stale key.\n        self._current_command_cache_key = None\n        self._conn.send_packed_command(command)\n\n    def send_command(self, *args, **kwargs):\n        self._process_pending_invalidations()\n\n        with self._cache_lock:\n            # Command is write command or not allowed\n            # to be cached.\n            if not self._cache.is_cachable(\n                CacheKey(command=args[0], redis_keys=(), redis_args=())\n            ):\n                self._current_command_cache_key = None\n                self._conn.send_command(*args, **kwargs)\n                return\n\n        if kwargs.get(\"keys\") is None:\n            raise ValueError(\"Cannot create cache key.\")\n\n        # Creates cache key.\n        self._current_command_cache_key = CacheKey(\n            command=args[0], redis_keys=tuple(kwargs.get(\"keys\")), redis_args=args\n        )\n\n        with self._cache_lock:\n            # We have to trigger invalidation processing in case if\n            # it was cached by another connection to avoid\n            # queueing invalidations in stale connections.\n            if self._cache.get(self._current_command_cache_key):\n                entry = self._cache.get(self._current_command_cache_key)\n\n                with self._pool_lock:\n                    while entry.connection_ref.can_read():\n                        try:\n                            entry.connection_ref.read_response(\n                                push_request=True,","sourceCodeStart":1762,"sourceCodeEnd":1798,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/connection.py#L1762-L1798","documentation":"Raised in CacheProxyConnection.send_command (connection.py:1779-1780) when the command is deemed cacheable by the cache (is_cachable returned True) but the caller did not pass keys=... . To build a CacheKey the proxy needs the keys the command operates on; if they are absent it cannot identify the cache entry, so it raises ValueError. This is an internal/library-side contract: cacheable commands must be dispatched with their keys.","triggerScenarios":"The cache proxy decides a command is cacheable (a read command configured as cacheable) but send_command was invoked without the keys kwarg that the proxy uses to build CacheKey. Typically hit by the higher-level client layer failing to pass keys for a read command when caching is enabled.","commonSituations":"A read command routed through the cache proxy without keys metadata (custom command path, a command not yet plumbed to carry keys), or a misconfiguration of which commands are cacheable.","solutions":["Ensure cacheable read commands are sent through the standard client API that supplies keys (e.g. r.get('k') not raw conn.send_command('GET')).","If using send_command directly with caching on, pass keys=('k',) for GET-style commands.","Review your cache config's cacheable-command list and remove commands you dispatch without keys.","Report/fix the command path so keys are propagated to the cache proxy."],"exampleFix":"# before (with caching on)\nconn.send_command('GET', 'mykey')  # no keys kwarg\n# after\nconn.send_command('GET', 'mykey', keys=('mykey',))","handlingStrategy":"validation","validationCode":"# When caching is on, route reads through the high-level API or pass keys\nif caching_enabled:\n    val = r.get('mykey')                 # client supplies keys\nelse:\n    conn.send_command('GET', 'mykey')     # direct is fine without cache\n\n# If you must call send_command directly with caching on:\nconn.send_command('GET', 'mykey', keys=('mykey',))","typeGuard":"def has_keys_kwarg(kwargs: dict) -> bool:\n    return kwargs.get('keys') is not None","tryCatchPattern":"try:\n    conn.send_command('GET', 'mykey', keys=('mykey',))\nexcept ValueError as e:\n    if 'Cannot create cache key' in str(e):\n        conn.send_command('GET', 'mykey', keys=('mykey',))\n    else:\n        raise","preventionTips":["Always pass keys= for cacheable commands when bypassing the client API.","Keep the cacheable-command set aligned with commands you actually dispatch with keys.","Prefer the high-level r.get/r.hget API so keys are propagated automatically."],"tags":["client-side-caching","internal","cache-key","validation"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}