ioredis
>=4.27.8postconditions62functions31last verified2026-06-23coverage score100%Postconditions: what we check
- Redis · missing-error-listenererrorWhenRedis instance created without error event listenerThrows
Errors emitted as events, not exceptions - silent failuresRequired handlingCaller MUST attach an error listener immediately after creating Redis instance: `redis.on('error', (err) => logger.error(err))`. Without this listener, connection errors are silently logged to console and may crash the application.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - Redis · connection-errors-not-handlederrorWhenConnection fails (ECONNREFUSED, ETIMEDOUT, ECONNRESET, ENOTFOUND, EPIPE, EAI_AGAIN)Throws
Error event emitted with connection errorRequired handlingCaller MUST handle connection errors in the error event listener. Common errors: ECONNREFUSED (Redis not running), ETIMEDOUT (network timeout), ECONNRESET (connection dropped), ENOTFOUND (DNS failure).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - get · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning - will crash in future Node.js versionsRequired handlingCaller MUST use try-catch (async/await) or .catch() on all Redis commands. Commands return promises that may reject due to: connection errors, WRONGTYPE, timeout, or serialization failures.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - get · command-timeoutwarningWhenCommand exceeds timeout (default: no timeout)Throws
MaxRetriesPerRequestError or CommandTimeoutErrorRequired handlingCaller SHOULD set commandTimeout option to prevent hanging operations. Without timeout, commands may hang indefinitely on network issues.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - set · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - pipeline · pipeline-results-not-checkederrorWhenPipeline exec() results not checked for errorsThrows
Individual command errors silently included in results arrayRequired handlingCaller MUST check each result in pipeline.exec() return value. Format: [[null, 'OK'], [Error, undefined], ...]. Pipeline does NOT reject on individual command failures.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - pipeline · pipeline-exec-unhandlederrorWhenPipeline exec() promise rejected without catchThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on pipeline.exec().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[6] - multi · watch-null-not-checkederrorWhenWATCH violation causes exec() to return null, not checkedThrows
null return value indicates transaction was abortedRequired handlingCaller MUST check if exec() returns null after using WATCH. Null indicates a watched key was modified, transaction aborted. This is NOT an error/rejection - it's a null return value.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7] - multi · exec-abort-not-handlederrorWhenEXECABORT error in transaction not handledThrows
EXECABORT error if queued command fails validationRequired handlingCaller MUST check exec() results for EXECABORT errors. Happens when queued commands fail validation (WRONGTYPE, etc.).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8] - subscribe · subscriber-mode-violationerrorWhenNon-pub/sub commands used on subscriber connectionThrows
Error: Connection in subscriber mode, only subscriber commands allowedRequired handlingCaller MUST use separate Redis connection for pub/sub. After subscribe(), only subscribe/unsubscribe/psubscribe/punsubscribe/quit/ping allowed. Use redis.duplicate() to create separate connection for normal commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - brpop · blocking-without-timeoutwarningWhenBlocking command without timeout parameterThrows
Command may block indefinitely, causing resource leakRequired handlingCaller SHOULD provide timeout parameter to blocking commands (BRPOP, BLPOP, BZPOPMIN, BZPOPMAX). Without timeout (or timeout=0), command blocks until data available, potentially forever. Recommended: Set reasonable timeout (e.g., 5-30 seconds).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[10] - connect · connect-promise-unhandlederrorWhenconnect() promise rejected without catchThrows
UnhandledPromiseRejectionWarning on connection failureRequired handlingCaller MUST use try-catch or .catch() on explicit connect() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[11] - hget · wrong-type-errorerrorWhenKey exists but is not a hashThrows
ReplyError with message containing 'WRONGTYPE'Required handlingCaller MUST catch WRONGTYPE errors for hash operations. This indicates key is not a hash - DO NOT retry. Fix data model or use correct command for key type.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - hget · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - hset · wrong-type-errorerrorWhenKey exists but is not a hashThrows
ReplyError with message containing 'WRONGTYPE'Required handlingCaller MUST catch WRONGTYPE errors. Key exists with different type - DO NOT retry.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - hset · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - hgetall · wrong-type-errorerrorWhenKey exists but is not a hashThrows
ReplyError with message containing 'WRONGTYPE'Required handlingCaller MUST catch WRONGTYPE errors. Key is not a hash - DO NOT retry.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - hgetall · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - lpush · wrong-type-errorerrorWhenKey exists but is not a listThrows
ReplyError with message containing 'WRONGTYPE'Required handlingCaller MUST catch WRONGTYPE errors. Key exists with different type - DO NOT retry.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - lpush · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - rpush · wrong-type-errorerrorWhenKey exists but is not a listThrows
ReplyError with message containing 'WRONGTYPE'Required handlingCaller MUST catch WRONGTYPE errors. Key exists with different type - DO NOT retry.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - rpush · unhandled-promise-rejectionerrorWhenCommand promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarningRequired handlingCaller MUST use try-catch or .catch() on all Redis commands.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - publish · publish-errorerrorWhenPublish fails (connection lost, etc.)Throws
Network error or UnhandledPromiseRejectionWarningRequired handlingCaller MUST catch publish errors with try-catch or .catch(). Network errors may be transient - implement retry logic. Returns number of subscribers that received the message (may be 0).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[13] - eval · script-errorerrorWhenLua script has syntax or runtime errorThrows
ReplyError with script error detailsRequired handlingCaller MUST catch Lua script errors. Script syntax errors should NOT be retried - fix script. Script runtime errors depend on logic - may or may not be retriable.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - eval · script-timeouterrorWhenLua script exceeds execution time limitThrows
ReplyError with timeout messageRequired handlingCaller MUST catch script timeout errors. Redis has lua-time-limit configuration (default 5 seconds). Optimize script or increase limit if needed. DO NOT retry immediately - may cause cascading timeouts.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - quit · quit-promise-unhandlederrorWhenquit() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning if connection already closed or network errorRequired handlingCaller MUST use try-catch or .catch() on quit(). quit() sends QUIT to server and waits for pending commands to complete. May reject if connection is already closed or broken before quit completes. Use disconnect() for immediate close if quit() is not available.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[14] - quit · quit-pending-commands-lostwarningWhenquit() called while commands are still queuedThrows
AbortError for queued commands that won't be executedRequired handlingCaller MUST be aware that quit() waits for pending replies but new commands added after quit() is called will receive AbortError. Do not enqueue commands after calling quit(). Pattern: await all pending operations before calling quit() in shutdown handlers.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[15] - del · del-unhandled-promise-rejectionerrorWhendel() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on del() calls. del() returns the count of deleted keys (0 if key doesn't exist — not an error). Returns Promise<number> — check return value only if you need to verify deletion.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - expire · expire-unhandled-promise-rejectionerrorWhenexpire() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on expire() calls. expire() returns 1 if timeout was set, 0 if key doesn't exist — 0 is NOT an error. Silent expiry failures leave data in Redis indefinitely, causing memory leaks and stale data bugs in session management and rate limiting.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - expire · expire-returns-zero-not-checkedwarningWhenexpire() called on non-existent key and return value not checkedThrows
N/A (returns 0, not an error)Required handlingCaller SHOULD check expire() return value when setting TTL on critical keys. Returns 0 if key does not exist — the set+expire pattern can fail silently if the key was never written or was already deleted between set() and expire() calls. Use SET with EX option (atomic) instead of separate set()+expire() calls: `redis.set(key, value, 'EX', ttlSeconds)` to avoid the race condition.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[16] - incr · incr-wrong-type-errorerrorWhenKey exists but is not a string or integerThrows
ReplyError with 'ERR value is not an integer or out of range'Required handlingCaller MUST catch WRONGTYPE/value-not-integer errors. Happens when key holds a non-integer string (e.g., JSON blob) and incr() is called. This is a programming error — do NOT retry. Fix the data model.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[17] - incr · incr-unhandled-promise-rejectionerrorWhenincr() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on incr() calls. Silent incr failures cause rate limiters to fail open (no limiting applied), which can lead to abuse, quota overruns, and billing errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - decr · decr-wrong-type-errorerrorWhenKey exists but is not an integer, or decrement would go below -9223372036854775808Throws
ReplyError with 'ERR value is not an integer or out of range'Required handlingCaller MUST catch value-not-integer and overflow errors. Overflow at 64-bit integer minimum causes ReplyError — not silent wrap-around. Fix data model or use DECRBY with range validation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[18] - decr · decr-unhandled-promise-rejectionerrorWhendecr() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on decr() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zadd · zadd-wrong-type-errorerrorWhenKey exists but is not a sorted setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors for sorted set operations. Key exists with different type — DO NOT retry. Fix data model.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zadd · zadd-nan-score-errorerrorWhenScore value is NaNThrows
ReplyError with 'not a float'Required handlingCaller MUST validate score before calling zadd(). NaN scores cause immediate ReplyError. Common when score comes from user input, division operations, or Date arithmetic. Validate: if (isNaN(score) || !isFinite(score)) throw before zadd().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[19] - zadd · zadd-unhandled-promise-rejectionerrorWhenzadd() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on zadd() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zrange · zrange-wrong-type-errorerrorWhenKey exists but is not a sorted setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors. Key is not a sorted set — DO NOT retry. Fix data model.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zrange · zrange-unhandled-promise-rejectionerrorWhenzrange() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on zrange() calls. Returns empty array if key doesn't exist — not an error.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zrem · zrem-wrong-type-errorerrorWhenKey exists but is not a sorted setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors. Returns 0 if member doesn't exist (not an error).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - zrem · zrem-unhandled-promise-rejectionerrorWhenzrem() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on zrem() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - sadd · sadd-wrong-type-errorerrorWhenKey exists but is not a setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors for set operations. Key exists with different type — DO NOT retry. Fix data model.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - sadd · sadd-unhandled-promise-rejectionerrorWhensadd() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on sadd() calls. Returns count of new members added (0 if already in set — not an error).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - smembers · smembers-wrong-type-errorerrorWhenKey exists but is not a setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors. Returns empty array if key doesn't exist — not an error. For large sets, consider SSCAN instead to avoid blocking Redis.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - smembers · smembers-large-set-blockingwarningWhenSet has thousands of members and smembers() is called in productionThrows
N/A — but Redis blocks for the duration, causing latency for all other clientsRequired handlingCaller SHOULD use sscanStream() for large sets instead of smembers(). smembers() returns ALL members in one blocking call. For sets > 1000 members, use cursor-based sscan() to avoid blocking Redis.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[20] - smembers · smembers-unhandled-promise-rejectionerrorWhensmembers() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on smembers() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - srem · srem-wrong-type-errorerrorWhenKey exists but is not a setThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors. Returns 0 if member doesn't exist (not an error).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - srem · srem-unhandled-promise-rejectionerrorWhensrem() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on srem() calls.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - blpop · blpop-blocking-without-timeoutwarningWhenblpop() called without timeout parameter or with timeout=0Throws
Command may block indefinitely, causing resource exhaustionRequired handlingCaller SHOULD always provide a non-zero timeout to blpop(). With timeout=0, command blocks until data is available — may be indefinite. In SaaS apps, this can exhaust connection pool and cause other commands to queue. Recommended: use a reasonable timeout (5-30 seconds) and loop to re-poll. Pattern: while (running) { const result = await redis.blpop('queue', 5); ... }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[21] - blpop · blpop-unhandled-promise-rejectionerrorWhenblpop() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection error or MaxRetriesPerRequestErrorRequired handlingCaller MUST use try-catch or .catch() on blpop() calls. Connection errors during blocking commands return MaxRetriesPerRequestError. Implement retry loop with backoff for job queue consumers.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[12] - blpop · blpop-subscriber-mode-conflicterrorWhenblpop() called on a connection in subscriber modeThrows
ReplyError: Connection in subscriber mode, only subscriber commands allowedRequired handlingCaller MUST NOT mix blpop() with pub/sub subscriptions on the same connection. Use a dedicated connection (redis.duplicate()) for job queue operations.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - exec · exec-results-not-checkederrorWhenexec() returned array but individual [error, result] tuples are not inspectedThrows
Per-command errors are silently embedded in the result array, NOT thrown by the outer PromiseRequired handlingCaller MUST iterate exec()'s return value and check each [error, result] tuple. Format: `[[null, 'OK'], [Error, undefined], [null, 42], ...]`. The outer Promise resolves successfully even when individual commands fail — the failures are hidden in the array. Skipping the per-tuple check silently drops failed writes (cache invalidation, rate-limit increments, job-enqueue) and leaves the application thinking the transaction succeeded. Pattern: const results = await tx.exec(); for (const [err, _] of results ?? []) if (err) handle(err);costmediumin prodsilent failureusers seelost datavisibilitysilent - exec · exec-null-return-not-checkederrorWhenexec() returns null after WATCH and return value is not checkedThrows
N/A — null is the success-but-aborted signal, not an errorRequired handlingCaller MUST check whether exec() returned null when WATCH was used. Null indicates a watched key was modified between WATCH and EXEC, so the transaction was discarded and NONE of the queued commands ran. Treating null as success corrupts optimistic-concurrency patterns (inventory decrement, balance updates, leader election) by silently dropping the retry that the WATCH/MULTI/EXEC pattern depends on. Pattern: const results = await tx.exec(); if (results === null) return retry();costmediumin prodsilent failureusers seelost datavisibilitysilent - exec · exec-unhandled-promise-rejectionerrorWhenexec() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection error or EXECABORTRequired handlingCaller MUST use try-catch or .catch() on tx.exec() / pipeline.exec(). The outer Promise CAN reject (separately from per-command errors) when the connection drops mid-EXEC or when EXECABORT fires because a queued command failed validation (WRONGTYPE on a queued op, queueing-time syntax errors). Unhandled rejection leaves the transaction in an unknown state — neither caller nor caller's caller knows whether the writes landed.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - exists · exists-unhandled-promise-rejectionerrorWhenexists() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection errorRequired handlingCaller MUST use try-catch or .catch() on exists() calls. A rejected exists() that the application reads as 'key missing' fails OPEN in idempotency-key and rate-limit code paths: the duplicate request gets processed, the throttle is bypassed. This is one of the highest-impact silent-failure modes in SaaS — connection blip during an idempotency probe lets the same Stripe charge fire twice. Pattern: never let a rejected probe be coerced into 'not present'.costhighin prodsilent failureusers seelost datavisibilitysilent - exists · exists-return-coerced-to-boolean-without-zero-checkwarningWhenexists() result is used in a truthy/falsy boolean check rather than compared to a specific countThrows
N/A — JavaScript coercion bug, not a Redis-thrown errorRequired handlingCaller SHOULD compare exists() return value to a specific number, not coerce to boolean. exists() returns a NUMBER, not a boolean. For multi-key probes (`exists(a, b, c)` returns 0, 1, 2, or 3), a truthy check `if (await redis.exists(a, b, c))` flags as 'all present' even when only 1 of 3 exists — a common bug in cache-aside patterns that require all keys to be present. Pattern: if ((await redis.exists(a, b, c)) === 3) usePopulatedCache();costlowin prodsilent failureusers seelost datavisibilitysilentSources[24] - getex · getex-wrong-type-errorerrorWhenKey exists but holds a non-string value (hash, list, set, sorted set, stream)Throws
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors on getex(). Like GET, getex() is a string-only command — calling it on a hash or list throws WRONGTYPE. This is a programming error: fix the data model or use the correct command for the actual key type. Do NOT retry — the type mismatch is deterministic.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - getex · getex-unhandled-promise-rejectionerrorWhengetex() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection error or server <6.2Required handlingCaller MUST use try-catch or .catch() on getex() calls. getex() rejects on the usual connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET) AND on 'ERR unknown command' if the connected Redis server is older than 6.2.0 — easy regression mode when an env points at a legacy Redis 6.0/6.1 instance. Without a catch, the session-refresh silently dies and the user gets logged out unexpectedly.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - getex · getex-invalid-ttl-argumentserrorWhenEX/PX/EXAT/PXAT argument is negative, zero, NaN, or non-finiteThrows
ReplyError with 'ERR invalid expire time in 'getex' command'Required handlingCaller MUST validate TTL arguments before passing to getex(). Negative seconds, NaN from Date arithmetic, or 0 cause immediate ReplyError. Common when the TTL is computed from `expiresAt - Date.now()` and the timestamp already elapsed — the rejected promise breaks session-refresh in a way that's hard to trace. Pattern: clamp/validate before call, or use PERSIST to clear TTL.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[25] - getdel · getdel-wrong-type-errorerrorWhenKey exists but holds a non-string valueThrows
ReplyError with 'WRONGTYPE Operation against a key holding the wrong kind of value'Required handlingCaller MUST catch WRONGTYPE errors on getdel(). Like GET and GETEX, getdel() is string-only — calling it on a hash, list, set, or sorted set throws WRONGTYPE. Do NOT retry. Fix the data model or use the correct command (HDEL/SPOP/LPOP) for the actual key type.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - getdel · getdel-unhandled-promise-rejectionerrorWhengetdel() promise rejected without catch handlerThrows
UnhandledPromiseRejectionWarning on connection error or server <6.2Required handlingCaller MUST use try-catch or .catch() on getdel() calls. Rejects on connection errors and on 'ERR unknown command' for Redis servers older than 6.2.0. Silent rejection in a one-time-token flow leaves the token in Redis (because the DELETE side never ran) AND returns undefined to the caller — the user sees "invalid token" but the token is still valid, blocking subsequent legitimate attempts. For magic-link flows this manifests as users unable to complete login after a transient Redis hiccup.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - getdel · getdel-null-return-not-distinguished-from-errorwarningWhengetdel() returns null and the application treats null as 'consumed by another worker' without distinguishing from connection failureThrows
N/A — null is the documented 'key did not exist' return; error case is a thrown rejectionRequired handlingCaller SHOULD log/metric the null-return case separately from the rejection case in token-consumption flows. A null return means 'key was never set OR was already consumed' — a legitimate outcome. A rejection means 'we don't know whether the key was consumed' — an ambiguous failure that needs operator visibility. Conflating them in logs (`if (!token) reject('invalid')`) hides connection issues behind a flood of legitimate-looking 'invalid token' errors.costlowin prodsilent failureusers seelost datavisibilitysilentSources[26]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [10]redis.io/commands/brpopBrpop
- [12]redis.io/docs/latest/developError Handling
- [16]redis.io/commands/expireExpire
- [17]redis.io/commands/incrIncr
- [18]redis.io/commands/decrDecr
- [19]redis.io/commands/zaddZadd
- [20]redis.io/commands/smembersSmembers
- [21]redis.io/commands/blpopBlpop
- [23]redis.io/commands/execExec
- [24]redis.io/commands/existsExists
- [25]redis.io/commands/getexGetex
- [26]redis.io/commands/getdelGetdel
- [1]github.com/redis/ioredisredis/ioredis
- [4]github.com/redis/ioredisredis/ioredis
- [5]github.com/redis/ioredisredis/ioredis
- [8]github.com/redis/ioredisredis/ioredis
- [9]github.com/redis/ioredisredis/ioredis
- [11]github.com/redis/ioredisredis/ioredis
- [14]github.com/redis/ioredisredis/ioredis
- [15]github.com/redis/ioredisredis/ioredis
- [22]github.com/redis/ioredisredis/ioredis
- [2]github.com/redis/ioredis/issuesredis/ioredis issue #321
- [3]github.com/redis/ioredis/issuesredis/ioredis issue #433
- [6]github.com/redis/ioredis/issuesredis/ioredis issue #753
- [7]github.com/redis/ioredis/issuesredis/ioredis issue #883
- [13]dev.to/franciscomendes10866/using-redis-pub-sub-with-node-js-13k3Using Redis Pub Sub With Node Js 13k3
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
ioredis Nark profile - Sources
Package: ioredis (Redis client for Node.js) Version Range: >=4.27.8 (minimum for prototype pollution fix) Last Verified: 2026-02-26 Research Thread: dev-notes/package-onboarding/ioredis/0001-research-ioredis.md
Official Documentation
-
ioredis GitHub Repository https://github.com/redis/ioredis Primary documentation for error handling, connection management, pipelines, transactions
-
Error Handling Guide https://redis.io/docs/latest/develop/clients/nodejs/error-handling/ Official Redis documentation for Node.js client error patterns
-
Pipelines and Transactions https://redis.io/docs/latest/develop/clients/nodejs/transpipe/ Pipeline result checking and transaction error handling
-
Redis Commands Reference https://redis.io/commands/ Individual command behaviors and error conditions
GitHub Issues (Error Patterns)
-
Issue #321: Connection errors not caught https://github.com/redis/ioredis/issues/321 Shows ECONNREFUSED and connection error patterns
-
Issue #433: Unhandled promise rejections https://github.com/redis/ioredis/issues/433 Command promises rejecting without .catch()
-
Issue #753: Pipeline errors silent https://github.com/redis/ioredis/issues/753 Pipeline exec() results not checked
-
Issue #883: WATCH null return not handled https://github.com/redis/ioredis/issues/883 Transaction null return from exec() when WATCH violated
-
Issue #944: Subscriber mode violations https://github.com/redis/ioredis/issues/944 Commands fail in subscriber mode, need duplicate()
-
Issue #1235: Command timeouts https://github.com/redis/ioredis/issues/1235 Commands hanging without timeout configuration
-
Issue #1478: Error listener required https://github.com/redis/ioredis/issues/1478 Missing error listener causes silent failures
-
Issue #1613: Blocking commands resource leak https://github.com/redis/ioredis/issues/1613 BRPOP/BLPOP without timeout blocking forever
-
Issue #1648: EXECABORT handling https://github.com/redis/ioredis/issues/1648 Transaction errors not checked in exec() results
-
Issue #1875: Connection retry exhaustion https://github.com/redis/ioredis/issues/1875 MaxRetriesPerRequestError not handled
-
Issue #2037: Serialization errors https://github.com/redis/ioredis/issues/2037 JSON.stringify errors in command arguments
Real-World Usage Analysis
-
Medusa SaaS Platform File:
test-repos/medusa/packages/modules/providers/caching-redis/src/services/redis-cache.tsPatterns observed: Connection health checking, pipeline usage, missing error handlers -
NestJS Framework Found ioredis usage in:
test-repos/nestjs/Integration patterns for Redis in enterprise applications -
TypeORM Found ioredis usage in:
test-repos/typeorm/Caching layer patterns
CVE Analysis
-
CVE-2025-49844: RediShell Remote Code Execution (Redis Server) https://nvd.nist.gov/vuln/detail/CVE-2025-49844 https://redis.io/blog/security-advisory-cve-2025-49844/ Scope: Redis server vulnerability (CVSS 10.0), not client-side Relevance: None - server security is separate concern Fixed in: Redis versions 6.2.20, 7.2.11, 7.4.6, 8.0.4, 8.2.2
-
CVE-2025-21605: DoS via Unbounded Buffers (Redis Server) https://nvd.nist.gov/vuln/detail/CVE-2025-21605 Scope: Redis server memory exhaustion Relevance: None - server-side only
-
SNYK-JS-IOREDIS-1567196: Prototype Pollution (ioredis Client) https://security.snyk.io/package/npm/ioredis https://security.snyk.io/vuln/SNYK-JS-IOREDIS-1567196 Scope: ioredis client vulnerability Relevance: HIGH - Affects client library Fixed in: ioredis 4.27.8+ Severity: High (7-8.9 CVSS)
-
Snyk Security Database - ioredis Vulnerabilities https://security.snyk.io/package/npm/ioredis Multiple high severity vulnerabilities documented Latest safe version: 5.9.3 (as of 2026-02-26)
Conclusion:
- ioredis client has prototype pollution vulnerability in versions < 4.27.8
- MINIMUM VERSION: 4.27.8
- RECOMMENDED VERSION: 5.9.3+ (latest stable)
- Redis server CVEs do not affect client behavior but should inform deployment security
Key Behavioral Insights
Critical Finding: Event-Based Error Model
ioredis uses event-based error emission instead of throwing exceptions for connection errors. This is fundamentally different from most Node.js libraries:
// ❌ WRONG - Errors silently logged to console
const redis = new Redis();
// ✅ REQUIRED - Explicit error listener
const redis = new Redis();
redis.on('error', (err) => {
logger.error('Redis connection error:', err);
});
Source: https://github.com/redis/ioredis#error-handling
Pipeline Result Checking
Pipeline exec() returns [[error, result], ...] format. Individual command errors do NOT cause rejection:
const results = await pipeline.exec();
// Check each result: results[0][0] is error, results[0][1] is value
Source: https://github.com/redis/ioredis#pipelines
Transaction Null Return
WATCH violations cause exec() to return null, NOT throw/reject:
await redis.watch('key');
// ... key modified by another client ...
const result = await redis.multi().set('key', 'value').exec();
if (result === null) {
// Transaction aborted due to WATCH violation
}
Source: https://github.com/redis/ioredis#transactions
Subscriber Mode Isolation
After subscribe(), only 5 commands work: subscribe, psubscribe, unsubscribe, punsubscribe, quit, ping. All others fail:
await redis.subscribe('channel');
await redis.get('key'); // ❌ Error: Connection in subscriber mode
Solution: Use redis.duplicate() for separate connection.
Source: https://github.com/redis/ioredis#pub/sub
Error Categories
Connection Errors (CRITICAL)
- ECONNREFUSED - Redis not running
- ETIMEDOUT - Network timeout
- ECONNRESET - Connection dropped
- ENOTFOUND - DNS resolution failed
- EPIPE - Broken pipe
- EAI_AGAIN - DNS temporary failure
Command Errors (CRITICAL)
- Promise rejection without .catch()
- WRONGTYPE - Operation on wrong data type
- Command timeout (MaxRetriesPerRequestError)
- Serialization errors (invalid arguments)
Pipeline Errors (CRITICAL)
- Unchecked results in exec() return
- Silent command failures in batch
Transaction Errors (HIGH)
- WATCH null return not checked
- EXECABORT in queued commands
- Result errors not validated
Subscriber Errors (HIGH)
- Mode violation (normal commands fail)
- Missing duplicate() for parallel operations
Blocking Command Issues (MEDIUM)
- BRPOP/BLPOP without timeout
- Resource leaks from indefinite blocking
Testing Strategy
Fixtures validate:
- Error listener requirement
- Promise rejection handling
- Pipeline result checking
- Transaction null checking
- Subscriber mode isolation
- Blocking command timeouts
Real-world validation against:
- medusa (26 repos total coverage)
- nestjs
- typeorm
- Additional SaaS applications
Related Packages
- redis - Official Redis client (different API)
- node-redis - Alternative client
- bull / bullmq - Job queues using Redis (separate contracts)
Research completed: 2026-02-24 Total sources: 20 references GitHub issues analyzed: 11 Real-world repos: 26 Confidence level: HIGH