Background
In the previous post (#5: Custom Envoy Proxy Deployment), I deployed a self-built Envoy into a Minikube cluster and compared the responses from accessing Redis directly versus going through the Envoy proxy. We confirmed that the bitfield_ro command is rejected as an unknown command by the proxy.
In this post, we will analyze where and how to fix that problem inside the Envoy Redis Proxy codebase. The goal is to pin down the exact modification points — not to modify code yet.
What is BITFIELD_RO?
BITFIELD_RO is the read-only variant of BITFIELD, added in Redis 6.0. While BITFIELD lets you read and write multiple bitfields in a single key through GET / SET / INCRBY subcommands, BITFIELD_RO only allows GET.
The key observation from a routing perspective is that both commands require hashing only one key — the first argument. That means Envoy Redis Proxy’s existing “single key → single shard” routing logic is enough. We do not need a new handler that merges responses. This judgment is the key conclusion of the analysis.
How commands are organized in Redis Proxy
Module layout
Let us first map out where the Redis Proxy code lives in the codebase.
| Path | Role |
|---|---|
source/extensions/filters/network/redis_proxy/ | The Redis proxy network filter itself (proxy, command splitter, router, connection pool) |
source/extensions/filters/network/common/redis/ | RESP codec, Redis client, command classification — shared primitives |
source/extensions/clusters/redis/ | Redis-specific cluster type (topology-aware LB) |
api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto | Filter config schema |
test/extensions/filters/network/redis_proxy/ | Unit and integration tests |
Request flow
Let us trace what happens when a client sends a bitfield_ro request.
proxy_filter.cc—ProxyFilter::onData()hands the incoming bytes to the RESP decoder (decoder_->decode)Once decoding finishes,
ProxyFilter::onRespValue()is called, which forwards the request viasplitter_.makeRequest()command_splitter_impl.cc—InstanceImpl::makeRequest()uses the command name to look up the corresponding handler in the lookup table
The handler hashes the key, picks an upstream pool from the router (
router_impl.cc), and sends the request through the connection pool (conn_pool_impl.cc)
Any command that is not registered in this lookup table at step 3 immediately gets an error response. The unknown command 'bitfield_ro' error we saw comes exactly from here.
How commands get registered
So who populates the lookup table? Command support is defined by SupportedCommands in common/redis/supported_commands.h, and the splitter reads that list at startup. Commands are divided into the following sets by routing strategy.


simpleCommands()— commands that hash a single key and go to one shard (get,set,bitfield, …)multiKeyCommands()— multi-key commands (del,mget,mset, …)evalCommands()—eval,evalshahashMultipleSumResultCommands()— commands that fan out to multiple shards and sum the results (exists, …)ClusterScopeCommands()— commands sent to every shard (flushall,config, …)randomShardCommands()— commands sent to a random shard (randomkey, …)transactionCommands()— transactional commands (multi,exec, …)writeCommands()— the set of write commands.isReadCommand()treats anything not in this set as a read command
And the command splitter, at startup, iterates these sets and auto-registers handlers for each entry.
| |

Note: Adding a new command therefore boils down to answering one question: “which set does this command belong to?” Once you put it in a set, the lookup table registration follows automatically.
Where to touch for adding bitfield_ro
As analyzed above, BITFIELD_RO only needs single-key hashing, so simpleCommands() is the right answer. Let us go file by file.
1. supported_commands.h — the heart of the change
| |

Tip: Since
BITFIELD_ROis read-only, do not add it towriteCommands().isReadCommand()is implemented as!writeCommands().contains(command), so simply leaving it out is enough to be treated as a read command. This decision affects later read-only routing policies, such as directing reads to replicas.
2. command_splitter_impl.cc — no changes needed
The InstanceImpl constructor iterates simpleCommands() in full and registers each one with simple_command_handler_, so adding to the set is enough. A new handler is only required for commands that need special response splitting or merging, like MGET or SCAN.

3. Stats and docs — no changes needed
- Per-command stats are not a fixed list — they extract the command name directly from the request (
RedisCommandStats::getCommandFromRequest), so no separate registration is needed. - The
redis_proxy_filter.rstdocumentation also does not enumerate the supported command list, so no documentation change is required.
How to verify
After making the change, run the build below and verify the command:
| |

Next post preview
The analysis is done. In the next post we will actually add bitfield_ro to supported_commands.h, get the tests passing, redeploy into the Minikube environment we built in week_5, and confirm that the unknown command error is gone.
fin.