redis/node-redis · warning · Error
TokenManager is not running, but a new token was received
Error message
TokenManager is not running, but a new token was received
What it means
handleNewToken is the .then continuation of identityProvider.requestToken(). If a requestToken() was in flight when stop() ran, its late resolution reaches handleNewToken after the listener is already null, and this guard throws. It is essentially a lifecycle race between teardown and an outstanding token fetch.
Solutions
- Drain or await the in-flight refresh before calling stop().
- Treat this error as expected during teardown and suppress it.
- Track an in-flight promise in stop() and resolve it before clearing the listener.
Defensive patterns
Strategy: try-catch
Try / catch
async function gracefulStop(manager) {
// ensure any in-flight requestToken resolves/rejects before clearing listener
await manager.drainInFlight?.(); // if available
manager.stop();
}
try {
await manager.refresh();
} catch (err) {
if (/not running/.test(err.message)) {
// expected during teardown
return;
}
throw err;
} Prevention
- Drain in-flight token requests before stopping the manager.
- Suppress 'not running' errors during documented teardown paths.
- Avoid stopping and immediately restarting the same manager; create a new one.
When it happens
Trigger: Calling stop() while requestToken() is still pending; the pending promise later resolves and tries to deliver a token to a manager with no listener.
Common situations: Process shutdown during a slow identity provider; test teardown that does not await in-flight refresh; reconnect logic that stops and immediately restarts the manager.
Related errors
- TokenManager is not running but received an error
- TokenManager is not running, but refresh was called
- expirationRefreshRatio must be greater or equal to 0
- expirationRefreshRatio must be less than or equal to 1
- The client is closed
AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11).
Data as JSON: /api/errors/899988cc93b0b762.
Report an issue: GitHub.
Appendix: source
Thrown at packages/client/lib/authx/token-manager.ts:238
await this.identityProvider.requestToken().then(this.handleNewToken);
this.retryAttempt = 0;
} catch (error) {
if (this.shouldRetry(error)) {
this.retryAttempt++;
const retryDelay = this.calculateRetryDelay();
this.notifyError(`Token refresh failed (attempt ${this.retryAttempt}), retrying in ${retryDelay}ms: ${error}`, true)
this.scheduleNextRefresh(retryDelay);
} else {
this.notifyError(error, false);
this.stop();
}
}
}
private handleNewToken = async ({ token: nativeToken, ttlMs }: TokenResponse<T>): Promise<void> => {
if (!this.listener) {
throw new Error('TokenManager is not running, but a new token was received');
}
const token = this.wrapAndSetCurrentToken(nativeToken, ttlMs);
this.listener.onNext(token);
this.scheduleNextRefresh(this.calculateRefreshTime(token));
}
/**
* Creates a Token object from a native token and sets it as the current token.
*
* @param nativeToken - The raw token received from the identity provider
* @param ttlMs - Time-to-live in milliseconds for the token
*
* @returns A new Token instance containing the wrapped native token and expiration details
*
*/
public wrapAndSetCurrentToken(nativeToken: T, ttlMs: number): Token<T> {
const now = Date.now();View on GitHub (pinned to 90fd0652bc)