redis/node-redis · error · TypeError

topologyRefreshOnReconnectionAttempt should return `false |

Error message

topologyRefreshOnReconnectionAttempt should return `false | undefined | number`, got ${delay} instead

What it means

Thrown from ClusterReconnectionTracker.#getDelay (cluster-reconnection-tracker.ts:119) at runtime, when a user-supplied topologyRefreshOnReconnectionAttemptStrategy FUNCTION returns a value that is not false | undefined | 0 and also not a non-negative integer. Unlike the constructor validation (error 28), this fires during a live reconnection attempt, not at construction.

Source

Thrown at packages/client/lib/cluster/cluster-reconnection-tracker.ts:119

   */
  #getDelay(firstReconnectionAt: number) {
    if (this.#strategy === undefined) {
      return ClusterReconnectionTracker.#DEFAULT_TOPOLOGY_REFRESH_ON_RECONNECTION_ATTEMPT;
    }

    if (this.#strategy === false) {
      return;
    }

    if (typeof this.#strategy === 'number') {
      return this.#strategy;
    }

    const delay = this.#strategy(firstReconnectionAt);
    if (delay === false || delay === undefined || delay === 0) return;

    if (!Number.isInteger(delay) || delay < 0) {
      throw new TypeError(`topologyRefreshOnReconnectionAttempt should return \`false | undefined | number\`, got ${delay} instead`);
    }

    return delay;
  }

  #clearTimestampIfClean() {
    if (this.#reconnectingClients.size === 0) {
      this.#firstReconnectionAt = undefined;
    }
  }
}

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Return false, undefined, or 0 to skip the refresh, or a non-negative integer (ms delay) — nothing else.
  2. Coerce/round inside the function: const d = Math.round(value); return Number.isInteger(d) && d >= 0 ? d : false;
  3. Unit-test the strategy against onReconnectionAttempt to catch bad returns before production.

Example fix

// before
strategy: (firstReconnectionAt) => `${Date.now() - firstReconnectionAt}` // string -> TypeError at runtime

// after
strategy: (firstReconnectionAt) => Math.max(0, Math.round(Date.now() - firstReconnectionAt))
Defensive patterns

Strategy: validation

Validate before calling

function safeStrategy(firstReconnectionAt: number) {
  const d = userFn(firstReconnectionAt);
  if (d === false || d === undefined || d === 0) return false;
  if (typeof d === 'number' && Number.isInteger(d) && d >= 0) return d;
  return false; // never let an invalid value escape
}

Type guard

function isValidDelay(d: unknown): boolean {
  return d === false || d === undefined || (typeof d === 'number' && Number.isInteger(d) && d >= 0);
}

Try / catch

cluster.on('error', (e) => {
  if (/topologyRefreshOnReconnectionAttempt should return/.test(e.message)) {
    // fix the strategy function's return type
  }
});

Prevention

When it happens

Trigger: Provide a function strategy that returns a string, negative number, float, true, or an object; a node reconnection occurs and onReconnectionAttempt evaluates the function's return value.

Common situations: Strategy functions reading config dynamically and returning a string delay; returning -1 to mean 'skip'; returning null instead of undefined/false/0.

Related errors


AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03). Data as JSON: /data/errors/01bc402333d2f87e.json. Report an issue: GitHub.