The Valkey clients page says that anything speaking the Redis 7.2 wire protocol works with Valkey 7.2 and later. For ordinary commands that holds. The exceptions sit where applications rarely test: the connection handshake, error text, and cluster topology replies. Against Valkey 9.1, go-redis 9.20.0 and earlier fail on CLUSTER SHARDS with redis: unexpected key "id" in CLUSTER SHARDS reply. Since 8.0, HELLO answers server: valkey, INFO prints server_mode instead of redis_mode, and the -LOADING, -BUSY and -MISCONF errors name Valkey. Patch releases on every branch have also changed replies on the wire. This post lists the changes from 8.0 to 9.1, labels what the 9.2 release candidate adds, names the client versions that handle them, and gives a test harness to run before you upgrade.
- go-redis before v9.20.1 cannot parse
CLUSTER SHARDSfrom Valkey 9.1, which always adds a shard-levelid. Cluster routing usesCLUSTER SLOTSand keeps working; directClusterShards()calls fail. - Since 8.0,
HELLOreportsvalkeyand the Valkey version,INFOprintsserver_mode, and fiveLOADING,BUSYandMISCONFerrors say Valkey.redis_versionstays7.2.4. extended-redis-compatibility yesrestores the Redis identity and those five errors, but not the new topology fields, andvalkey.confsays it will be removed.- Patch releases change replies too: strict CRLF parsing,
COMMAND INFORESP3 types,LSETrange errors, and oversized clusterPUBLISHrejection. - Replay the commands your application really sends against the old and new versions, and diff the replies, before the server changes.
Scope and review date: November 2026. This post covers Valkey 8.0 through 9.1.3, the current GA release (26 October 2026), and labels behaviour from the 9.2 release candidate (9.2.0-rc2, 4 November 2026). Release notes, Valkey server source at the 9.0.7, 9.1.3 and 9.2.0-rc2 tags, and client library source were read on 10 November 2026. The commands were checked against that documentation and source, not run in a lab, so run the harness at the end on staging.
Why do client libraries break when Valkey says it is compatible?
Because compatibility is promised for the protocol, and the breaks happen in parsers and string matches built on top of it. The Valkey clients page says "most existing clients need no changes to migrate". The project's own issue #4891, opened 14 October 2026, says there is no shared contract for client behaviour, and that the client-changes-needed label already holds 74 issues and pull requests.
Three kinds of client code break. A strict parser rejects a map key it does not know. An error handler matches the full message text instead of the error code. A feature gate reads the server name or version from HELLO or INFO and takes a different path.
Where a strict CLUSTER SHARDS parser fails on Valkey 9.1
Follow the numbers: the server reply is valid, the client parser rejects it
Routing is not the failure point: the go-redis cluster client builds its slot map from CLUSTER SLOTS. Code that calls ClusterShards() itself, such as operators and admin tools, is what fails.
What do HELLO and INFO report on Valkey 8.0 and 9.1?
Valkey 7.2.x still answered HELLO with server: redis. From 8.0, the server source returns server: valkey and the real Valkey version, such as 9.1.3. INFO server renames redis_mode to server_mode, and keeps redis_version:7.2.4 on every release. The 9.1.3 version.h calls it the Redis OSS compatibility version that "should never exceed 7.2.x".
Other identity fields arrived in steps. server_name, valkey_version and an availability_zone line (empty by default) are in INFO server from 8.0. valkey_release_stage appears in 8.1. HELLO adds availability_zone in 8.1, and only when the availability-zone config is set.
HELLO 3 and INFO server on a Valkey 9.1.3 node
Illustrative output, shaped from the 9.1.3 source: the id and module list vary
Trust valkey_version: redis_version is a frozen compatibility value. The HELLO reference page still shows "server" => "redis" in its examples; the server source does not.
# Record what each node reports before and after the upgrade
for h in vk-1 vk-2 vk-3; do
echo "== $h"
valkey-cli -h "$h" INFO server \
| grep -E '^(redis_version|server_name|valkey_version|server_mode|redis_mode):'
doneInterpretation: on 8.0 and later you should see server_mode, not redis_mode. A dashboard or health check that reads redis_mode gets nothing. Any script that gates on redis_version treats every Valkey release as 7.2.4.
One more 9.1 change shows up here. 9.1 moved Lua into a module that the server loads at startup. Reading the 9.1.3 source, it is registered like any other module, so expect MODULE LIST and the modules field of HELLO to list lua. Check this on staging if a tool treats a non-empty module list as "Redis Stack".
What does extended-redis-compatibility change, and what does it not?
The extended-redis-compatibility config, added in 8.0 and off by default, makes Valkey present itself as Redis. In the 9.1.3 source it switches three things: the HELLO server name and version, the INFO mode field, and five shared error strings (LOADING, three BUSY variants, and MISCONF). It does not touch the Lua error texts, transaction errors, or the new CLUSTER SHARDS and CLUSTER SLOTS fields.
The same commands on Valkey 8.0, 9.1, and 9.1 in compatibility mode
Switch panels to see which replies a client parser will meet
Valkey 8.0
| Reply | Valkey 8.0 |
|---|---|
HELLO server | valkey |
HELLO version | 8.0.x |
HELLO availability_zone | Never sent (added in 8.1) |
INFO mode field | server_mode |
INFO redis_version | 7.2.4 |
-LOADING text | Valkey is loading the dataset in memory |
CLUSTER SHARDS keys per shard | slots, nodes |
Node availability-zone in CLUSTER SHARDS and SLOTS | Never sent |
CLIENT REPLY inside MULTI | Accepted |
Valkey 9.1
| Reply | Valkey 9.1 |
|---|---|
HELLO server | valkey |
HELLO version | 9.1.3 |
HELLO availability_zone | Sent when availability-zone is set |
INFO mode field | server_mode |
INFO redis_version | 7.2.4 |
-LOADING text | Valkey is loading the dataset in memory |
CLUSTER SHARDS keys per shard | slots, nodes, id |
Node availability-zone in CLUSTER SHARDS and SLOTS | Sent when availability-zone is set |
CLIENT REPLY inside MULTI | Rejected; EXEC then fails with EXECABORT (since 9.0) |
9.1, compatibility on
| Reply | 9.1 with extended-redis-compatibility yes |
|---|---|
HELLO server | redis |
HELLO version | 7.2.4 |
HELLO availability_zone | Sent when availability-zone is set |
INFO mode field | redis_mode |
INFO redis_version | 7.2.4 |
-LOADING text | Redis is loading the dataset in memory |
CLUSTER SHARDS keys per shard | slots, nodes, id |
Node availability-zone in CLUSTER SHARDS and SLOTS | Sent when availability-zone is set |
CLIENT REPLY inside MULTI | Rejected; EXEC then fails with EXECABORT (since 9.0) |
Not a full rollback: compatibility mode fixes name and version checks only. A strict topology parser still breaks on 9.1, with the setting on or off.
# Runtime switch; affects HELLO and error replies from the next command on
valkey-cli CONFIG SET extended-redis-compatibility yes
valkey-cli CONFIG GET extended-redis-compatibility
# Persist it only if you need it across restarts
valkey-cli CONFIG REWRITEInterpretation: the config is MODIFIABLE_CONFIG in the 9.1.3 source, so no restart is needed. It also changes client behaviour you may not expect. valkey-go v1.0.79 reads the major version from HELLO and uses CLUSTER SHARDS for topology only when it is 8 or higher, so with compatibility on, it falls back to CLUSTER SLOTS.
The 9.1.3 valkey.conf calls it "a temporary configuration added in Valkey 8.0" that "is scheduled to have no effect and will be removed in a future version". It still exists in 9.2.0-rc2. Use it to buy time while you fix clients, not as the fix.
Why does go-redis fail on CLUSTER SHARDS against Valkey 9.1?
Valkey 9.1.0 added two fields to CLUSTER SHARDS: a shard-level id, which is always present, and a node-level availability-zone, sent only when the node has one configured. The CLUSTER SHARDS reference lists both under 9.1.0. In the source, 9.0.7 writes each shard as a 2-key map and 9.1.3 as a 3-key map. CLUSTER SLOTS also gained availability-zone inside its networking metadata map.
go-redis 9.20.0 and earlier parse the CLUSTER SHARDS reply with a switch that returns an error on any unknown key. Because the shard id is always there, ClusterShards() against any 9.1 cluster returns redis: unexpected key "id" in CLUSTER SHARDS reply, whether or not you set an availability zone. Valkey issue #3909 (opened 3 June 2026) is still open with the label major-decision-deferred. The maintainers chose to keep the fields and wait to see whether more users report problems. The fix went into the client: go-redis v9.20.1 (11 June 2026) skips unknown shard and node keys.
# Shape of the reply on a cluster node (jq prints keys sorted)
valkey-cli -3 --json CLUSTER SHARDS | jq '.[0] | keys'
# 9.0.x: ["nodes", "slots"]
# 9.1.x: ["id", "nodes", "slots"]Interpretation: if the third key is there, any client or tool that rejects unknown keys will fail on this node. Run it on one upgraded staging node before you touch production.
# Which go-redis version does each service build with, and who calls ClusterShards?
go list -m github.com/redis/go-redis/v9
grep -rn --include='*.go' 'ClusterShards(' .Interpretation: a version below v9.20.1 plus any ClusterShards( hit is a confirmed break on 9.1. With no hits, the cluster client still routes correctly, because loadState in go-redis v9.20.0 builds the slot map from CLUSTER SLOTS. Upgrade anyway, so a later dependency that calls it does not surprise you.
Which error replies changed between 8.0 and 9.1?
The error codes did not change; the text after them did. Valkey 8.0 removed "Redis" from error replies (8.0.0 release notes, #206) and made nested transactions abort (#723). 9.0 added more transaction and auth ordering changes (9.0.0-rc1 notes).
| Since | Before | After |
|---|---|---|
| 8.0 | LOADING Redis is loading the dataset in memory | LOADING Valkey is loading the dataset in memory |
| 8.0 | BUSY Redis is busy running a script... (and the FUNCTION and module variants) | BUSY Valkey is busy running a script... |
| 8.0 | MISCONF Redis is configured to save RDB snapshots... | MISCONF Valkey is configured to save RDB snapshots... |
| 8.0 | Lua: Unknown Redis command called from script | Unknown command called from script |
| 8.0 | Lua: Wrong number of args calling Redis command from script | Wrong number of args calling command from script |
| 8.0 | MULTI inside MULTI: ERR MULTI calls can not be nested, transaction stays open | ERR Command 'multi' not allowed inside a transaction, and EXEC fails with EXECABORT |
| 8.0 | WATCH inside MULTI: ERR WATCH inside MULTI is not allowed | ERR Command 'watch' not allowed inside a transaction, transaction aborted |
| 9.0 | CLIENT REPLY ON|OFF|SKIP inside MULTI accepted | Rejected, transaction aborted |
| 9.0 | Unknown command before AUTH: ERR unknown command | NOAUTH first (#1475) |
One change came and went. 8.0.0 and 8.0.1 answered UNSUBSCRIBE from a client that was not subscribed with NOSUB 'unsubscribe' command executed not in subscribed mode. The 8.0.2 notes revert it as "an unintended breaking change". If you still run 8.0.0 or 8.0.1, move to the latest 8.0 patch.
# Find code and tests that match the old message text
grep -rnE "Redis is (loading|busy|configured)|Unknown Redis command|calling Redis command|MULTI calls can not be nested|WATCH inside MULTI|redis_mode" \
./src ./testsInterpretation: every hit is a handler or assertion that stops matching on 8.0 and later. Retry logic keyed on the "Redis is loading" text is the risky one: it silently stops retrying while a node loads its dataset.
Key handlers on the first word (LOADING, BUSY, EXECABORT, NOAUTH). redis-py v8.1.0, for example, maps errors on the first word, so LOADING still raises BusyLoadingError on Valkey. Its ERR mappings still compare full message text.
# The nested transaction change, on a 9.1 staging node
valkey-cli -3 <<'EOF'
MULTI
SET k 1
MULTI
EXEC
EOF
# 8.0 and later: the second MULTI returns
# ERR Command 'multi' not allowed inside a transaction
# and EXEC returns EXECABORT, so SET k 1 is not appliedInterpretation: on Redis 7.2 and Valkey 7.2, the same sequence ran SET k 1. Client wrappers that open a transaction without tracking whether one is already open will now lose writes, so check them.
Which patch releases change replies on the wire?
Valkey backports fixes to every maintained branch at once, and several of them change what a client receives. Each row below is from the release notes; "first fixed in" is per branch. The most recent wave (7.2.15, 8.0.12, 8.1.11, 9.0.7 and 9.1.3) shipped on 26 and 27 October 2026.
| Change | First fixed in | What a client sees |
|---|---|---|
| Strict CRLF parsing (#2872) | 7.2.14, 8.0.10, 8.1.8, 9.0.5, 9.1.0 | Requests ending in a bare LF get a protocol error instead of being misparsed |
COMMAND INFO subcommands in RESP3 (#3939) | 7.2.14, 8.0.10, 8.1.9, 9.0.5, 9.1.1 | An empty Array instead of a Set for commands without subcommands |
HPERSIST on a wrong-type key (#3516) | 9.0.5, 9.1.0 | A valid error instead of a malformed reply that desynchronized the connection |
| Corrupted replies with I/O threads (#4060) | 9.0.5, 9.1.1 | No more dropped leading bytes in replies |
| Torn RESP3 push frames on self-publish (#4253) | 9.0.6, 9.1.2 | Push frames arrive whole when a client publishes to a channel it subscribes to |
BLPOP followed by a partial pipelined command (#4531) | 9.0.6, 9.1.2 | No permanent client hang |
LSET out-of-range index on 64-bit builds (#4823) | 7.2.15, 8.0.12, 8.1.11, 9.0.7, 9.1.3 | An index out of range error instead of a silent write to the wrong element |
Oversized PUBLISH and SPUBLISH in cluster mode (#5030) | 7.2.15, 8.0.12, 8.1.11, 9.0.7, 9.1.3 | An error reply (a security fix, GHSA-f4q9-j22m-xqq9) |
# See the RESP3 type of a reply: sets are numbered with "~", arrays with ")"
valkey-cli -3 --no-raw COMMAND INFO GETInterpretation: in the subcommands field of GET, an older patch prints an empty set and a fixed one prints an empty array. --json renders both as [], so use --no-raw when the RESP3 type matters to your parser.
What does the Valkey 9.2 release candidate change for clients?
These are 9.2 release candidate behaviours (9.2.0-rc1 on 16 September 2026, rc2 on 4 November 2026), not GA. Plan tests for them, but expect changes before 9.2.0 ships. From the 9.2.0-rc2 and rc1 notes:
- Large sorted sets use a B+ tree, so
OBJECT ENCODINGreturnsbtreeinstead ofskiplist(#4359). ZRANGEBYSCORE,ZREVRANGEBYSCORE,ZCOUNTandZRANGESTOREreject a bare(or an empty string as a score bound instead of treating it as 0 (#4054).- Inline commands containing a raw NUL byte get a protocol error (#4759).
- Cluster replicas redirect keyless commands such as
SCANandFLUSHDBto the primary for clients that sentCLIENT CAPA REDIRECTand notREADONLY(#3505). Issue #4891 calls this a breaking change. SISMEMBERgains anXXoption that returns-1when the key does not exist (#2972).
# Code that asserts on encodings or builds score bounds from user input
grep -rnE "OBJECT ENCODING|skiplist|ZRANGEBYSCORE|ZCOUNT" ./src ./tests
valkey-cli COMMAND DOCS SISMEMBERInterpretation: tests that expect skiplist fail on 9.2. Also note a metadata error: the 9.2.0-rc2 sismember.json, which feeds COMMAND DOCS, dates XX to 9.1.0, but 9.1.3 has a fixed arity of 3 and no XX. Feature-detect with a probe on the real server, not with the since history.
Which client versions handle these changes?
We read the source or release notes of these clients on 10 November 2026. This is not a full survey. Issue #4891 notes that each library picks its own topology command and retry rules, so check yours the same way.
| Client or tool | Version read | What it does |
|---|---|---|
| go-redis | v9.20.0, v9.20.1 (latest v9.23.0) | v9.20.0 and earlier fail on unknown CLUSTER SHARDS keys; v9.20.1 skips them. Cluster routing uses CLUSTER SLOTS |
| valkey-go | v1.0.79 | Uses CLUSTER SHARDS for topology when the HELLO major version is 8 or more; reads keys by name and ignores extras |
| redis-py | v8.1.0 | Maps errors by the first word, so the 8.0 text changes after LOADING or BUSY do not change the exception class |
| Valkey GLIDE | README on main (latest release v2.5.3) | The supported engine table lists Valkey 7.2 through 9.1 |
| redis_exporter | v1.97.0 | Reads server_mode when redis_mode is missing and adds a valkey_version label; its redis_version label stays 7.2.4 |
# Which libraries and versions are connected right now?
valkey-cli CLIENT LIST \
| grep -oE 'lib-name=[^ ]* lib-ver=[^ ]*' | sort | uniq -c | sort -rnInterpretation: this counts connections by the library name and version that clients sent with CLIENT SETINFO. An empty lib-name means the client did not send it, so check that service by hand. The server field is lib-ver; the CLIENT LIST reference calls it lib-version, so a parser written from the docs misses it.
# Versions each service ships, from its own repository
go list -m all | grep -E 'go-redis|valkey-go|valkey-glide'
pip show redis valkey valkey-glide 2>/dev/null | grep -E '^(Name|Version)'
npm ls ioredis iovalkey redis @valkey/valkey-glideInterpretation: include batch jobs, operators and admin tools. The go-redis break on 9.1 is most likely in tooling, not in the main application.
How do you test client compatibility before upgrading?
Replay the commands your application sends against the current and target versions, diff the replies, then run the client's own tests against the target. Build the command list from what the application really sends: capture it with MONITOR on staging (never on a busy primary), or from integration test traffic.
Client compatibility test before a Valkey upgrade
Eight steps on staging; nothing here touches production
-
Inventory connected clients
Count connections by library and version from production, read-only.
valkey-cli CLIENT LIST | grep -oE \ 'lib-name=[^ ]* lib-ver=[^ ]*' | sort | uniq -c -
Capture a command corpus
Run MONITOR on staging during an integration test, then strip it to one command per line.
timeout 60 valkey-cli -h staging MONITOR > monitor.log -
Start the current and target versions
Use cluster mode so topology replies are included. Repeat with 9.1.3 as vk-new.
docker run -d --name vk-old valkey/valkey:9.0.7 \ valkey-server --cluster-enabled yes -
Make each a one-node cluster
Assign all slots so CLUSTER SHARDS and CLUSTER SLOTS return data.
docker exec vk-old valkey-cli \ CLUSTER ADDSLOTSRANGE 0 16383 -
Replay the corpus and diff
Run the script below. Ignore lines that always differ, such as ids and timestamps.
./replay.sh corpus.txt vk-old vk-new -
Set an availability zone and rerun
This exercises the optional HELLO and topology fields.
docker exec vk-new valkey-cli \ CONFIG SET availability-zone zone-a -
Run each service's tests against vk-new
Pin the client versions found in step 1, and include error-path tests.
-
Upgrade clients, then servers
Ship client fixes first; they also work against the old server.
Main risk: a reply your corpus never requests. Build it from captured traffic, not from the commands you expect the application to send.
#!/usr/bin/env bash
# replay.sh CORPUS OLD_CONTAINER NEW_CONTAINER
# CORPUS: one command per line, no quoted spaces, throwaway data only
set -euo pipefail
corpus=$1; shift
for c in "$@"; do
: > "replies-$c.txt"
while IFS= read -r cmd; do
echo "### $cmd" >> "replies-$c.txt"
# word splitting of $cmd is intended
docker exec "$c" valkey-cli -3 --no-raw $cmd \
>> "replies-$c.txt" 2>&1 < /dev/null || true
done < "$corpus"
done
diff -u "replies-$1.txt" "replies-$2.txt" | grep -vE '"id"|run_id|server_time|uptime' || trueInterpretation: every remaining hunk is a reply that differs between versions; check each against the parsers of the clients from step 1. Start the corpus with HELLO 3, INFO server, CLUSTER SHARDS, CLUSTER SLOTS and COMMAND INFO GET, then add what MONITOR captured.
Frequently asked questions
Does Valkey 9.1 break go-redis?
Only for code that calls ClusterShards(). Valkey 9.1 adds a shard-level id to every CLUSTER SHARDS reply, and go-redis 9.20.0 and earlier return "unexpected key" errors on it. The go-redis cluster client routes with CLUSTER SLOTS, so normal reads and writes keep working. go-redis v9.20.1, released 11 June 2026, skips unknown keys, so upgrade before moving to 9.1.
Why does INFO show redis_version 7.2.4 on Valkey 9.1?
It is a fixed compatibility value. Valkey's source defines the Redis OSS compatibility version as 7.2.4 and says it should never exceed 7.2.x, so every Valkey release reports it. Read valkey_version for the real version, and server_name to tell Valkey from Redis. HELLO reports the Valkey version unless extended-redis-compatibility is on.
Should I turn on extended-redis-compatibility?
Only as a short-term workaround. It makes HELLO report redis and 7.2.4, restores redis_mode in INFO, and puts "Redis" back in five LOADING, BUSY and MISCONF errors. It does not hide the new CLUSTER SHARDS fields. Valkey's configuration file calls it temporary and says it will be removed, so fix the clients instead.
Can a Valkey patch release change client behaviour?
Yes. Patch releases on every branch have changed replies on the wire: strict CRLF parsing, the RESP3 type of a COMMAND INFO field, an LSET out-of-range error, and an error for oversized PUBLISH in cluster mode. Most are bug or security fixes, but a client that relied on the old behaviour changes too. Diff the replies on staging before patching.
Key takeaways
- Move go-redis to v9.20.1 or later before Valkey 9.1, and find every direct
ClusterShards()call in tools and operators. - Read
valkey_versionandserver_mode;redis_versionis always 7.2.4 andredis_modeis gone from 8.0. - Match errors on the code, not the text: 8.0 changed the
LOADING,BUSY,MISCONF, Lua and nested transaction messages. - Treat patch releases and the 9.2 release candidate as client-visible changes, and read their notes before you roll them out.
- Replay captured traffic against the current and target versions, diff the replies, and upgrade clients before servers.
Check your Valkey clients with JusDB
JusDB's Valkey support team can review your client libraries and tooling against the 8.0 to 9.1 changes, help build and run a replay test on staging, and plan the client and server upgrade order for your Sentinel or cluster setup.
Bring your current Valkey versions, the client libraries each service uses, and any operators or admin tools that talk to the cluster. Your team approves every production change.