redis/node-redis · error · WatchError
Client reconnected after WATCH
Error message
Client reconnected after WATCH
What it means
Thrown from _executeMulti (client/index.ts:1932) when the socket epoch recorded at WATCH time no longer equals the current socketEpoch. The epoch increments on every reconnect, so a mismatch means the underlying TCP connection dropped and was re-established between WATCH and EXEC. Redis discards all watches when the connection closes, so the client cannot honor the optimistic lock and fails fast instead of silently running EXEC unguarded.
Source
Thrown at packages/client/lib/client/index.ts:1932
selectedDB?: number
) {
assertNoHimportSessionCommands(commands);
const dirtyWatch = this._self.#dirtyWatch;
this._self.#dirtyWatch = undefined;
const watchEpoch = this._self.#watchEpoch;
this._self.#watchEpoch = undefined;
if (!this._self.#socket.isOpen) {
throw new ClientClosedError();
}
if (dirtyWatch) {
throw new WatchError(dirtyWatch);
}
if (watchEpoch && watchEpoch !== this._self.socketEpoch) {
throw new WatchError('Client reconnected after WATCH');
}
const batchSize = commands.length;
return trace(CHANNELS.TRACE_BATCH,
async () => {
const typeMapping = this._commandOptions?.typeMapping;
const chainId = Symbol('MULTI Chain');
const promises: Array<Promise<unknown>> = [
this._self.#queue.addCommand(['MULTI'], { chainId }),
];
for (const { args } of commands) {
promises.push(
this._self.#queue.addCommand(args, {
chainId,
typeMapping
})View on GitHub (pinned to bb5beb5657)
Solutions
- Catch WatchError and replay the WATCH + transaction from the start on the fresh connection.
- Stabilize the connection: raise socketTimeout, tune reconnectStrategy, or fix the underlying network.
- Shorten the WATCH→EXEC window so reconnects are less likely to land inside it.
Example fix
// before
await client.watch('k');
// ... network blip triggers reconnect ...
await client.multi().set('k', 'v').exec(); // throws WatchError('Client reconnected after WATCH')
// after
async function optimisticTxn(client) {
for (;;) {
await client.watch('k');
try {
return await client.multi().set('k', 'v').exec();
} catch (e) {
if (/reconnected after WATCH/.test(e.message)) continue;
throw e;
}
}
} Defensive patterns
Strategy: retry
Validate before calling
if (client.isWatching && client.socketEpoch !== /* captured at watch */) {
// connection changed since WATCH; expect WatchError
} Type guard
function isReconnectWatchError(e: unknown): boolean {
return e instanceof Error && /reconnected after WATCH/i.test(e.message);
} Try / catch
for (let i = 0; i < N; i++) {
await client.watch('k');
try { return await client.multi().set('k','v').exec(); }
catch (e) { if (isReconnectWatchError(e) && i < N-1) continue; throw e; }
} Prevention
- Stabilize the link (socketTimeout, reconnectStrategy) to avoid mid-transaction reconnects.
- Replay the whole WATCH+EXEC on reconnect-style WatchError.
- Avoid holding WATCH open across long awaits.
When it happens
Trigger: Call client.WATCH(key); a network blip/reconnect happens before multi().exec() resolves; the epoch check at EXEC time throws WatchError('Client reconnected after WATCH').
Common situations: Unstable network or a Redis server restart between WATCH and EXEC; long transactions on flaky links; aggressive socket timeouts that trigger reconnects mid-transaction.
Related errors
- One (or more) of the watched keys has been changed
- Unknown RESP type ${type} "${String.fromCharCode(type)}"
- Reconnect strategy should return `false | Error | number`, g
- topologyRefreshOnReconnectionAttempt must be undefined, fals
- topologyRefreshOnReconnectionAttempt should return `false |
AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03).
Data as JSON: /data/errors/8f9a3b4fa7c37782.json.
Report an issue: GitHub.