Error reply catalog
A lookup table of every wire-level error reply kevy emits, what triggers it, and what the client should do next.
How to read this
kevy errors travel over RESP as a simple-error string: -<PREFIX> <message>\r\n. The first whitespace-separated token is the prefix (ERR, WRONGTYPE, MOVED, CROSSSLOT, ...). Client libraries typically surface this as an error variant or exception whose .kind / .code / class name matches the prefix; the rest of the line is a human-readable message.
This catalog is grouped by prefix. If you handle errors structurally (recommended), match on the prefix; the trailing message is intended for logs and operators, not for parsing.
Errors are part of kevy's user-facing contract. Adding, renaming, or repurposing a prefix is a breaking change for clients that pattern-match.
Core reference
| Prefix | Triggers when | Recovery / next step | ||
|---|---|---|---|---|
-ERR unknown command '<cmd>' | Command name is not implemented or not recognized. | Check README command coverage; verify spelling. | ||
-ERR wrong number of arguments for '<cmd>' command | argc doesn't match the command's accepted shapes. | Re-issue with the documented argument count. | ||
-ERR value is not an integer or out of range | INCR/DECR-class command on a value that isn't a parsable i64, or numeric arg out of range. | Use SET to overwrite with a parsable integer; clamp the input. | ||
-ERR no such key | RENAME / RENAMENX / COPY / GETEX targets a key that does not exist. | Treat the key as absent (use EXISTS to pre-check if needed). | ||
-ERR kevy only supports DB 0 | SELECT N issued with N ≠ 0. | kevy has no multi-DB; use separate instances or namespaced keys. | ||
-ERR MULTI calls can not be nested | MULTI sent while already inside a MULTI block. | Wait for EXEC / DISCARD before opening another transaction. | ||
-ERR EXEC without MULTI | EXEC sent with no open transaction. | Pair with MULTI, or drop the command. | ||
-ERR DISCARD without MULTI | DISCARD sent with no open transaction. | Pair with MULTI, or drop the command. | ||
-ERR WATCH inside MULTI is not allowed | WATCH sent inside a MULTI block. | Issue WATCH before MULTI. | ||
-ERR <cmd> not allowed inside MULTI | A command that isn't queue-safe (pub/sub, WATCH, HELLO, RENAME) was queued inside MULTI. | Issue these outside the transaction. | ||
-ERR Protocol error | Inbound bytes are not valid RESP. | Reconnect and retry; if persistent, audit the client serializer. | ||
-ERR CONFIG SET failed for '<key>': <reason> | Unknown CONFIG field or value out of range. | CONFIG GET * to see supported fields and current values. | ||
-ERR CONFIG REWRITE could not write <path>: <io-error> | Config TOML path is missing or not writable. | Check --config path and filesystem permissions. | ||
-WRONGTYPE Operation against a key holding the wrong kind of value | Command run against an existing key of a different Redis type. | See Wrong-type rules. | ||
-EXECABORT Transaction discarded because of previous errors. | A command queued during MULTI was an unknown verb or had too few arguments; EXEC refuses the batch and runs nothing. | Fix the offending queued command, then MULTI / queue / EXEC again. | ||
-MOVED <slot> <host:port> | Key's hash slot is not owned by this node. | See Cluster-routing replies. | ||
-CROSSSLOT Keys in request don't hash to the same slot | Multi-key command spans more than one hash slot. | See Cluster-routing replies. | ||
-MISDIRECTED writer is <host:port> | Write landed on a node that doesn't own this key's scope, or REPL.WAIT could not serve read-your-writes on this replica (timeout or generation mismatch). | See Cluster-routing replies; for REPL.WAIT, read the primary at <host:port>. | ||
-QUIESCED migrating to <host:port> | The slot or scope is mid-migration and frozen on this node, or the node is mid-FAILOVER handover to <host:port>. | See Cluster-routing replies. | ||
-OOM command not allowed when used memory > 'maxmemory' | Write-class command with policy noeviction after the limit is exceeded. | Raise maxmemory, set an eviction policy (e.g. allkeys-lru), or DEL to free room. Existing data is intact. | ||
-READONLY You can't write against a read only replica. | Write command sent to a replica node. | Send to the primary, or use a routing client. | ||
-NOREPLICAS Not enough good replicas to write. | Primary with min_replicas_to_write = N sees fewer than N healthy replicas. | Back off and retry; restore the replicas (see docs/availability.md). | ||
-NOREPLICAS primary lost quorum; writes fenced | An elect-quorum primary cannot reach a strict majority of its peers (partition minority side); writes self-fence for the lease window. | Back off and retry — the fence lifts when the partition heals, or the majority elects a new primary your routing client will find. | ||
-STALE replica is stale; read the primary or raise replica_max_staleness_ms | Read sent to a replica whose last primary heartbeat is older than its replica_max_staleness_ms bound. | Read the primary until the replica catches up, or raise/disable the bound. | ||
-READONLY can't write against a read-only script | Script was evaluated via EVAL_RO / EVALSHA_RO and attempted a write. | Use the writable EVAL / EVALSHA variant. | ||
-NOSCRIPT No matching script. Please use EVAL. | EVALSHA <sha> requested a script not in the cache. | Call EVAL directly (kevy auto-caches) or SCRIPT LOAD first. | ||
-LOADING kevy is loading the dataset in memory | Read sent to a replica while it is receiving a full-resync snapshot from its primary. | Wait and retry (the window is bounded by the snapshot ship); PING, INFO, and HELLO are answered during loading, so health checks and monitoring keep working. | ||
-ERR No such client address in the list | Legacy-form CLIENT KILL <addr:port> matched no connection. | List live connections with CLIENT LIST and re-issue with an existing addr; the filtered form (`CLIENT KILL ID\ | ADDR\ | LADDR …`) returns a count of 0 instead of erroring. |
Prefixes kevy never emits
By design, kevy does not authenticate or authorize at the protocol layer; these Redis-compatible prefixes are deliberately absent:
-NOAUTH— noAUTHcommand surface.-WRONGPASS— no password check.-NOPERM— no ACL system.-MISCONF— a persistence failure (AOF append or background save) is logged to stderr and the node keeps serving from its consistent in-memory state; kevy does not turn a durability error into a client-visible reply. On restart it replays whatever reached disk.-BUSY— kevy has no interactiveSCRIPT KILL. A script is instead bounded by an instruction budget ([lua] time_limit_ms,0= unlimited); an over-budget script is aborted in place and itsEVALreturns an ordinary-ERRLua error, so a runaway script never wedges the shard for another client to interrupt.
If your client expects these to be possible, treat them as unreachable on kevy. Access control is delegated to the deployment perimeter (kevy binds 127.0.0.1 by default).
Wrong-type rules
A WRONGTYPE reply means the key already exists with a different Redis data type than the command expects. The rules:
- Type is decided at key creation and persists until the key is deleted (or expires).
DEL <key>followed by the original command will succeed (you've reset the type).EXPIRE/PERSIST/TYPE/OBJECT ENCODING/EXISTS/DEL/UNLINKare type-agnostic and never raiseWRONGTYPE.WRONGTYPEis never returned for a missing key; missing-key semantics follow each command's documented behavior (GETreturns nil,LPUSHcreates the list, and so on).
Recovery is always one of: pick a different key, or DEL the existing one and re-create with the intended type.
Cluster-routing replies
These prefixes only appear when kevy is running in a routed mode (cluster or scoped). A non-cluster client may never see them.
-MOVED <slot> <host:port>— Fires when a key's hash slot is permanently owned by another node. The client should reconnect to<host:port>and re-issue. Cluster-aware clients (e.g.,redis-cli -c,ioredisin cluster mode) follow the redirect transparently and update their slot map.-CROSSSLOT Keys in request don't hash to the same slot— Fires on multi-key commands (MGET,MSET,DELwith multiple keys,EVALwith multipleKEYS,SUNIONSTORE, etc.) when the keys do not all hash to the same slot. Co-locate the keys with a shared{hashtag}segment, or split the command into per-slot batches client-side.-MISDIRECTED writer is <host:port>— Fires on a write that landed on a node that does not own the key's scope. Routing clients follow the redirect; manual clients should reconnect to<host:port>.-QUIESCED migrating to <host:port>— Fires while a slot or scope is being migrated and is frozen on this node. The client should treat it likeMOVEDand retry against<host:port>. Once migration completes, that node will respond authoritatively.
See the protocol notes in docs/ for the full routing model.
FAQ
My client treats -MOVED as a fatal error — how do I fix it? The client isn't cluster-aware. Either switch to a cluster-aware client (e.g., redis-cli -c, ioredis with Cluster, redis-py with RedisCluster, the kevy routing client), or wrap your driver to catch MOVED, reconnect to the host in the message, and re-issue.
Single-key command came back with -CROSSSLOT — is that a bug? No. CROSSSLOT only fires for multi-key commands. If you see it on what looks like a single-key call, the command is actually multi-key (e.g., EVAL with two KEYS, SUNIONSTORE with source + destination). Use {tag} notation to force shared slot placement, or split the call.
I got -OOM — is my data corrupt? No. -OOM is rejected at command-admission time; the write never landed. The keyspace is in exactly the state it was before the command. Free room (DEL / set an eviction policy / raise maxmemory) and retry.
-LOADING keeps coming back — how long should I wait? For as long as the full-resync snapshot ship takes (proportional to dataset size and link speed). PING, INFO, and HELLO are answered during loading (INFO replication reports loading:1), so health checks still work. A replica only enters this state when it reconnects too far behind the primary's backlog; if -LOADING recurs constantly, raise [replication] replication_buffer_size or investigate the link.
A queued MULTI command returned -EXECABORT — were any writes applied? No. EXECABORT means the transaction was rejected as a batch; nothing in the queued sequence was executed. Fix the offending command and reopen with MULTI.
Extension surfaces (IDX. / VIEW. / FEED.)
The extension verbs follow the same prefix contract. Every error is self-explaining: it names the verb and the object and points at the discovery surface (an agent that hits one can recover in-band).
| Prefix | Emitted when | Recovery |
|---|---|---|
ERR <VERB> '<name>': bad arguments — run COMMAND DOCS <VERB> for the syntax | argument parse failed on an IDX./VIEW. verb | COMMAND DOCS <verb> returns the full syntax string |
ERR no such index '<name>' (IDX.LIST enumerates them) | query names an index that doesn't exist | IDX.LIST / VIEW.LIST enumerate the catalog |
INDEXBUILDING index '<name>' is still building (poll IDX.LIST until state=ready) | query raced the post-create backfill | poll IDX.LIST state; see docs/migration.md |
INDEXOVERBUDGET index '<name>' build exceeded MAXMEM (raise maxmemory or DROP the index) | build hit the memory budget | raise maxmemory or IDX.DROP |
FEEDRESYNC <gen> <tail> | a FEED cursor is no longer servable (generation bump or past-backlog) | restart consumption from a fresh snapshot + the returned cursor; see docs/cdc.md |
Updating this catalog
If you add or modify a code path that emits a -<PREFIX> ... reply:
- Update the row in Core reference (or add a new one).
- Extend the wire-level chaos test at crates/kevy/tests/wire_torture_chaos.rs if a new prefix is introduced.
- Note the change in CHANGELOG.md for the release that ships it.
Errors are part of the client contract. Silent message changes can break ecosystem libraries that pattern-match on them.