{"record":{"id":"95e8345b27b44230","repo":"redis/node-redis","slug":"expirationrefreshratio-must-be-less-than-or-equal","errorCode":null,"errorMessage":"expirationRefreshRatio must be less than or equal to 1","messagePattern":"expirationRefreshRatio must be less than or equal to 1","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/authx/token-manager.ts","lineNumber":146,"sourceCode":" * TokenManager is responsible for obtaining/refreshing tokens and notifying listeners about token changes.\n * It uses an IdentityProvider to request tokens. The token refresh is scheduled based on the token's TTL and\n * the expirationRefreshRatio configuration.\n *\n * The TokenManager should be disposed when it is no longer needed by calling the dispose method on the Disposable\n * returned by start.\n */\nexport class TokenManager<T> {\n  private currentToken: Token<T> | null = null;\n  private refreshTimeout: NodeJS.Timeout | null = null;\n  private listener: TokenStreamListener<T> | null = null;\n  private retryAttempt: number = 0;\n\n  constructor(\n    private readonly identityProvider: IdentityProvider<T>,\n    private readonly config: TokenManagerConfig\n  ) {\n    if (this.config.expirationRefreshRatio > 1) {\n      throw new Error('expirationRefreshRatio must be less than or equal to 1');\n    }\n    if (this.config.expirationRefreshRatio < 0) {\n      throw new Error('expirationRefreshRatio must be greater or equal to 0');\n    }\n  }\n\n  /**\n   * Starts the token manager and returns a Disposable that can be used to stop the token manager.\n   *\n   * @param listener The listener that will receive token updates.\n   * @param initialDelayMs The initial delay in milliseconds before the first token refresh.\n   */\n  public start(listener: TokenStreamListener<T>, initialDelayMs: number = 0): Disposable {\n    if (this.listener) {\n      this.stop();\n    }\n\n    this.listener = listener;","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/authx/token-manager.ts#L128-L164","documentation":"TokenManagerConfig.expirationRefreshRatio controls when a token is refreshed, expressed as a fraction of the token's TTL in [0,1]. A value greater than 1 would schedule a refresh after the token has already expired, which is nonsensical and is rejected in the TokenManager constructor.","triggerScenarios":"Constructing new TokenManager(identityProvider, { expirationRefreshRatio: 1.2, retryPolicy: {...} }) with a value above 1.","commonSituations":"Confusing the ratio (0..1) with a percentage (0..100); copying config from a percent-based library; typo or arithmetic slip computing the value.","solutions":["Use a value in [0,1] (typical 0.7..0.9 to refresh before expiry with margin).","If you meant 80 percent, write 0.8.","Validate the ratio at config load time so the failure is localized."],"exampleFix":"// before\nnew TokenManager(idp, { expirationRefreshRatio: 80, retryPolicy });\n\n// after\nnew TokenManager(idp, { expirationRefreshRatio: 0.8, retryPolicy });","handlingStrategy":"validation","validationCode":"function validateTokenManagerConfig(cfg) {\n  const r = cfg.expirationRefreshRatio;\n  if (typeof r !== 'number' || Number.isNaN(r) || r > 1) {\n    throw new Error(`expirationRefreshRatio must be in [0,1], got ${r}`);\n  }\n  return cfg;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember expirationRefreshRatio is a ratio in [0,1], not a percentage.","Centralise token config validation at app startup.","Document the unit next to the config field in your own settings file."],"tags":["config","auth","validation","token-manager"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}