redis/node-redis · error · Error

expirationRefreshRatio must be greater or equal to 0

Error message

expirationRefreshRatio must be greater or equal to 0

What it means

Companion guard to error [3]: the TokenManager constructor rejects `expirationRefreshRatio < 0`. A negative ratio would compute a negative refresh delay, breaking the scheduling math (Math.floor of a negative ttlMs fraction) and effectively never refreshing correctly. Both bounds checks fire in the constructor, so the TokenManager never enters an inconsistent scheduling state.

Source

Thrown at packages/client/lib/authx/token-manager.ts:149

 *
 * The TokenManager should be disposed when it is no longer needed by calling the dispose method on the Disposable
 * returned by start.
 */
export class TokenManager<T> {
  private currentToken: Token<T> | null = null;
  private refreshTimeout: NodeJS.Timeout | null = null;
  private listener: TokenStreamListener<T> | null = null;
  private retryAttempt: number = 0;

  constructor(
    private readonly identityProvider: IdentityProvider<T>,
    private readonly config: TokenManagerConfig
  ) {
    if (this.config.expirationRefreshRatio > 1) {
      throw new Error('expirationRefreshRatio must be less than or equal to 1');
    }
    if (this.config.expirationRefreshRatio < 0) {
      throw new Error('expirationRefreshRatio must be greater or equal to 0');
    }
  }

  /**
   * Starts the token manager and returns a Disposable that can be used to stop the token manager.
   *
   * @param listener The listener that will receive token updates.
   * @param initialDelayMs The initial delay in milliseconds before the first token refresh.
   */
  public start(listener: TokenStreamListener<T>, initialDelayMs: number = 0): Disposable {
    if (this.listener) {
      this.stop();
    }

    this.listener = listener;
    this.retryAttempt = 0;

    this.scheduleNextRefresh(initialDelayMs);

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Set expirationRefreshRatio to a value in [0,1] (e.g. 0.7).
  2. Validate config at load time and reject non-finite or out-of-range values before constructing the TokenManager.
  3. If using a sentinel default, coerce it to a valid ratio (e.g. 0.7) when unset.

Example fix

// before
new TokenManager(provider, { expirationRefreshRatio: -1 });

// after
new TokenManager(provider, { expirationRefreshRatio: 0.7 });
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isFinite(cfg.expirationRefreshRatio) || cfg.expirationRefreshRatio < 0) {
  throw new RangeError('expirationRefreshRatio must be a finite number >= 0');
}

Type guard

function isNonNegativeRatio(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
}

Prevention

When it happens

Trigger: Passing `expirationRefreshRatio: -1` or any negative number; an arithmetic slip (subtracting instead of adding, or 0 - value) producing a negative config; reading an unset numeric env var parsed as NaN (note: NaN comparisons are false, so NaN slips past, but a parsed negative literal triggers this).

Common situations: Default/uninitialized config expressed as -1 'sentinel'; sign error in config transformation; env var typo producing a leading minus.

Related errors


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