redis/node-redis · error · Error

expirationRefreshRatio must be less than or equal to 1

Error message

expirationRefreshRatio must be less than or equal to 1

What it means

The TokenManager constructor validates that `expirationRefreshRatio` (the fraction of a token's lifetime after which a refresh is scheduled) is within [0,1]. A value > 1 is meaningless and dangerous — it would schedule a refresh after the token has already expired, leaving a window with no valid token — so the constructor throws synchronously before the manager is usable.

Source

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

 * TokenManager is responsible for obtaining/refreshing tokens and notifying listeners about token changes.
 * It uses an IdentityProvider to request tokens. The token refresh is scheduled based on the token's TTL and
 * the expirationRefreshRatio configuration.
 *
 * 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;

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Set expirationRefreshRatio to a value in [0,1], typically 0.5–0.8 (refresh when 50–80% of lifetime has elapsed).
  2. If your value is a percentage, divide by 100 before passing: `expirationRefreshRatio: pct / 100`.
  3. Add a unit test / config schema asserting the ratio is within [0,1].

Example fix

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

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

Strategy: validation

Validate before calling

function makeTokenManager(provider, cfg) {
  if (!(cfg.expirationRefreshRatio >= 0 && cfg.expirationRefreshRatio <= 1)) {
    throw new RangeError('expirationRefreshRatio must be within [0, 1]');
  }
  return new TokenManager(provider, cfg);
}

Type guard

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

Prevention

When it happens

Trigger: Constructing `new TokenManager(provider, { expirationRefreshRatio: 1.5 })` or any value strictly greater than 1; computing the ratio from config/env where the math inverts (e.g. dividing by a small number) and yields >1; passing a percentage (e.g. 75) instead of a ratio (0.75).

Common situations: Config mistake: treating the field as a percentage (75) rather than a ratio (0.75); copy-paste from a doc example with a typo; dynamic config where an operator enters '120' expecting percent; refactoring that flips numerator/denominator.

Related errors


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