InletDownload

Redis error EXECABORT

EXECABORT Transaction discarded because of previous errors.

A command you queued after MULTI was refused straight away (a typo, wrong arguments, no permission, no memory), so at EXEC Redis threw the whole transaction away and ran none of it. The real cause is the error that came before EXEC.

EXECABORT Transaction discarded because of previous errors.

Tested on Redis 8.10.2 and Valkey 8.1.10 · Updated 9 October 2026

What it means

MULTI starts a transaction. The commands after it don’t run yet: Redis checks each one, replies QUEUED, and keeps it. EXEC then runs them all in one go, with nothing from other clients in between.

If a command fails that check (an unknown command, the wrong number of arguments, a command your user may not run, a write the server can’t accept right now), Redis replies with that error at once and marks the transaction. At EXEC it replies EXECABORT and runs none of the commands, not even the ones that queued fine. So EXECABORT itself tells you little: the cause is the error printed earlier, right after the bad command.

Errors that only show up while EXEC runs are different, and catch people out:

  • Run-time errors don’t abort. INCR on a value that isn’t a number, or a list command on a string (WRONGTYPE), queue without complaint. At EXEC, that one command fails, its error takes its place in the reply, and every other command in the transaction still runs. Redis has no rollback.
  • WATCH doesn’t produce an error. If a key you watched changed before EXEC, EXEC replies with a null ((nil) in redis-cli) and runs nothing. Your code is meant to retry.

Common causes

  1. A typo or wrong arguments in a queued command: INCR with no key, INCRBY with an extra argument, a misspelt command name.
  2. A command the user may not run, or a key it may not use: the queued command gets NOPERM.
  3. The server is out of memory (maxmemory reached with the noeviction policy): a queued write gets OOM.
  4. Writing to a read-only replica: a queued write gets READONLY.
  5. On Valkey, a nested MULTI or a WATCH inside the transaction. Valkey 8 refuses both with ERR Command not allowed inside a transaction and aborts the transaction. Redis 8 refuses them too (ERR MULTI calls can not be nested, ERR WATCH inside MULTI is not allowed), but still runs the rest at EXEC.

On a cluster, a transaction whose keys are in different slots fails at EXEC with CROSSSLOT instead.

How to fix it

Find the first error

In redis-cli the cause is the (error) line between MULTI and EXEC. Client libraries usually send the whole transaction at once and show only the final error, but keep the earlier one:

  • ioredis rejects with ReplyError: EXECABORT Transaction discarded because of previous errors. and puts the queuing errors in the error’s previousErrors array.
  • redis-py raises the first queuing error itself rather than ExecAbortError (from its source).

Fix that command, then run the whole transaction again: nothing from the first attempt was applied.

Check every reply after EXEC

Because run-time errors don’t abort, a successful EXEC can still contain failures. Check each element of its reply. If some commands must not run unless others succeed, validate first, or move the logic into a Lua script or function, where you can check values before writing.

Retry when WATCH fires

The usual pattern for check-and-set:

WATCH balance:1
GET balance:1
MULTI
SET balance:1 <new value>
EXEC

If EXEC replies (nil), someone changed balance:1 after your WATCH: read it again and retry. redis-py raises WatchError for this (Watched variable changed.).

Don’t nest transactions

Send WATCH before MULTI, and finish a transaction with EXEC or DISCARD before starting the next. The two servers treat a mistake here differently, so code that works on Redis can hit EXECABORT on Valkey.

Reproduce it

Redis 8.10.2, redis-cli 8.10.2, database 15:

MULTI
OK
SET seo:auth:t1 a
QUEUED
INCR
(error) ERR wrong number of arguments for 'incr' command
EXEC
(error) EXECABORT Transaction discarded because of previous errors.
GET seo:auth:t1
(nil)

The SET had queued, but it never ran. An INCRBY with an extra argument and a misspelt SETT gave ERR wrong number of arguments for 'incrby' command and ERR unknown command 'SETT', with args beginning with: 'seo:auth:t1' 'b', then the same EXECABORT. Run-time errors, by contrast:

MULTI
OK
SET seo:auth:t2 a
QUEUED
INCR seo:auth:t2
QUEUED
LPUSH seo:auth:t2 x
QUEUED
SET seo:auth:t3 b
QUEUED
EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) OK
MGET seo:auth:t2 seo:auth:t3
1) "a"
2) "b"

WATCH: one session ran WATCH seo:auth:w, a second client then ran SET seo:auth:w 20, and the first session went on:

MULTI
OK
SET seo:auth:w 11
QUEUED
EXEC
(nil)
GET seo:auth:w
"20"

Valkey 8.1.10 gave the same replies, except for a nested MULTI and a WATCH inside the transaction. After MULTI and a queued SET, Redis 8.10.2 replied:

(error) ERR MULTI calls can not be nested
(error) ERR WATCH inside MULTI is not allowed
1) OK

and Valkey 8.1.10:

(error) ERR Command not allowed inside a transaction
(error) ERR Command not allowed inside a transaction
(error) EXECABORT Transaction discarded because of previous errors.

From Node.js, ioredis 5.11.1 with a transaction holding a SET and an INCR with no key, on both servers:

ReplyError: EXECABORT Transaction discarded because of previous errors.
previousErrors: [ "ERR wrong number of arguments for 'incr' command" ]

On temporary Redis 8.10.2 servers, a queued write was refused in three other ways, each followed by EXECABORT at EXEC: by a user limited to other keys (NOPERM No permissions to access a key), on a server started with --maxmemory 1mb (OOM command not allowed when used memory > 'maxmemory'.), and on a replica (READONLY You can't write against a read only replica.).

In Inlet

When the server refuses a command, Inlet shows the message after the EXECABORT code and links to this page. Edits you make in the key grid don’t need WATCH: each change is checked and applied atomically by a Lua script that first compares the value with what Inlet loaded, so a value someone changed in the meantime isn’t overwritten.

Related

Sources