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
The companion guard to error 4: expirationRefreshRatio must also be >= 0. A negative ratio has no meaning (refresh before token issuance) and is rejected by the TokenManager constructor.
Solutions
- Clamp the value at a sensible lower bound (e.g. 0.5 to refresh in the first half of TTL).
- Fix the upstream calculation that produced a negative number.
- Validate at config load time.
Example fix
// before expirationRefreshRatio: ttl < 0 ? ttl / expected : 0.8 // after expirationRefreshRatio: 0.8
Defensive patterns
Strategy: validation
Validate before calling
function validateTokenManagerConfig(cfg) {
const r = cfg.expirationRefreshRatio;
if (typeof r !== 'number' || Number.isNaN(r) || r < 0 || r > 1) {
throw new Error(`expirationRefreshRatio must be in [0,1], got ${r}`);
}
return cfg;
} Prevention
- Validate the ratio is a finite number in [0,1] before constructing TokenManager.
- Watch for arithmetic that can underflow (e.g. ttl / something where something is huge).
- Default to a safe value like 0.8 when in doubt.
When it happens
Trigger: Constructing TokenManager with expirationRefreshRatio set to a negative number, e.g. -0.1.
Common situations: Sign error when computing the ratio from another value; underflow of a computed expression; copy-paste mistake.
Related errors
- expirationRefreshRatio must be less than or equal to 1
- TokenManager is not running, but refresh was called
- Client Side Caching is only supported with RESP3
- emitInvalidate is not supported (or necessary) when…
- emitInvalidate is only supported with RESP3
AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11).
Data as JSON: /api/errors/7199dbd01cffff52.
Report an issue: GitHub.
Appendix: 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 90fd0652bc)