Documentation for agents: an index at /llms.txt, every docs page in one file at /llms-full.txt, and Markdown for any docs page by adding .md to its URL.

Effect KitRedis

Errors and retries

Handle typed Redis failures and decide what is safe to retry from the delivery field.

Commands fail with tagged errors in the error channel. Most of them carry a delivery field that says how far the command got before it failed.

Example (The Error Type of a Command)

import {  } from "effect";
import {  } from "@effect-kit/redis";

const  = .(function* () {
  const  = yield* ;
  return yield* .("greeting");
});

type Failure = .<typeof >;
type Failure = ClusterError | CommandInputError | ReplyError | SentinelError | ServerError | SessionError | UnsupportedCommandError | UpstashError | ExecutionError

The error type is RedisError: a union of tagged errors, each with a _tag you can match on. Connection setup errors surface when the layer is built, not on a command.

What does delivery mean?

delivery has four values.

ValueMeaningWhere it appears
unsentNothing was written for this command.Native and HTTP
possibly-writtenThe command may have reached the server, and no reply was read.Native only
repliedThe server answered.Native and HTTP
unknownThe request may have reached the server, and no reply was read.Upstash HTTP only

possibly-written and unknown mean the same thing on different transports. Both leave the command in doubt: it can have run once, or not at all.

Errordelivery
CommandInputError, AdmissionError, HandshakeError, UnsupportedCommandError, ProfileErrorunsent
ConnectionErrorunsent or possibly-written
SessionErrorunsent, replied or possibly-written
ClusterError, SentinelErrorunsent or replied
ServerError, ReplyErrorreplied
UpstashErrorunsent, unknown or replied

ProtocolError has no delivery field. Malformed framing retires the connection it came from. The error reference lists every field and reason.

Handling errors by tag

Match a tag with Effect.catchTag. A ServerError carries the server's error code, and its reply text is redacted because it can contain application data.

Example (Treating a Wrong Type as a Missing Value)

import { ,  } from "effect";
import {  } from "@effect-kit/redis";

const  = .(function* () {
  const  = yield* ;
  return yield* .("user:42:name");
}).(
  .("ServerError", () =>
    . === "WRONGTYPE" ? .(.<string>()) : .(),
  ),
);

ServerError and ReplyError have delivery: "replied". The command reached the server and got an answer, so there is nothing in doubt.

What the package retries

The package retries only things it can prove are safe. It never replays a command.

SourceWhat it retriesLimit
Opening a connectionA TransportError while opening, except reason: "limit". A HandshakeError is not retriedconnection.reconnectAttempts (default 3), waiting reconnectDelayMillis * 2^attempt (default 100 ms), capped at 60000 ms
Cluster redirectsA command the server answered with MOVED or ASK, sent to the node it namescluster.maxRedirects (default 8), then ClusterError with reason: "redirect-limit"
Lua scriptsAn EVALSHA the server rejected with NOSCRIPT, after loading the scriptOnce. A second rejection is returned as is
Pub/Sub with recovery: "resubscribe"Restoring subscriptions after a TransportError or ConnectionErrorsubscriptions.reconnectAttempts (default 3), spaced by reconnectDelayMillis (default 100 ms)
Sentinel discoveryAsking the sentinels for the primarysentinel.discoveryAttempts (default 3), spaced by retryDelayMillis (default 100 ms)

The first is connection setup, not a command. The next two re-send a command only after the server replied that it did not run it.

Everything else is yours to retry. In particular, no command is retried after a write that may have reached the server:

  • A ConnectionError with delivery: "possibly-written" is returned to you as is. Interrupting a command after it was written retires that connection, and cannot undo the mutation.
  • The Upstash HTTP layer retries nothing. A 429 response becomes UpstashError with reason: "rate-limit" and, when the server sent a Retry-After header, retryAfterSeconds.
  • A Sentinel failover does not replay a command whose outcome is uncertain.

No retry after a possible write

A command that failed with possibly-written or unknown may have run. Retrying INCR or LPUSH blindly can apply it twice.

Retrying a command that never reached the server

delivery: "unsent" means nothing was written, so a retry cannot duplicate the command. Retry with Effect.retry and a while predicate on the error.

Example (Retrying a Write That Was Never Sent)

import { ,  } from "effect";
import {  } from "@effect-kit/redis";

const  = .(function* () {
  const  = yield* ;
  return yield* .("visits");
}).(
  .({
    : .("200 millis"),
    : 3,
    : () => . === "ConnectionError" && . === "unsent",
  }),
);

Other unsent errors, such as CommandInputError, fail the same way every time. Restrict the predicate to errors where waiting helps: ConnectionError, and AdmissionError with reason: "count" or "bytes", which mean the command queue is full.

Retrying an idempotent command

You know which commands are safe to run twice. A read is, and so is SET with a fixed value. For these, retry on possibly-written too.

Example (Retrying a Read After an Uncertain Failure)

import { ,  } from "effect";
import {  } from "@effect-kit/redis";

const  = .(function* () {
  const  = yield* ;
  return yield* .("greeting");
}).(
  .({
    : .("200 millis"),
    : 3,
    : () => . === "ConnectionError",
  }),
);

For a non-idempotent command, read the state back before deciding. After a possibly-written INCR, GET the counter and compare, or design the write so a second run changes nothing.

Pipelines and transactions

A pipeline returns one Result per command, so one failure does not hide the others. If the whole batch fails, BatchError carries redacted per-command outcomes, and its execution field ("partial-or-unknown", "not-executed", "unknown" or "replied") says whether any command may have run. TransactionError has execution: "not-executed" or "unknown" for the same purpose. See Pipelines and Transactions.