{"record":{"id":"b85993fb596a6e0a","repo":"redis/node-redis","slug":"tokenmanager-is-not-running-but-refresh-was-calle","errorCode":null,"errorMessage":"TokenManager is not running, but refresh was called","messagePattern":"TokenManager is not running, but refresh was called","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/authx/token-manager.ts","lineNumber":216,"sourceCode":"\n    if (this.retryAttempt >= maxAttempts) {\n      return false;\n    }\n\n    if (isRetryable) {\n      return isRetryable(error, this.retryAttempt);\n    }\n\n    return false;\n  }\n\n  public isRunning(): boolean {\n    return this.listener !== null;\n  }\n\n  private async refresh(): Promise<void> {\n    if (!this.listener) {\n      throw new Error('TokenManager is not running, but refresh was called');\n    }\n\n    try {\n      await this.identityProvider.requestToken().then(this.handleNewToken);\n      this.retryAttempt = 0;\n    } catch (error) {\n\n      if (this.shouldRetry(error)) {\n        this.retryAttempt++;\n        const retryDelay = this.calculateRetryDelay();\n        this.notifyError(`Token refresh failed (attempt ${this.retryAttempt}), retrying in ${retryDelay}ms: ${error}`, true)\n        this.scheduleNextRefresh(retryDelay);\n      } else {\n        this.notifyError(error, false);\n        this.stop();\n      }\n    }\n  }","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/authx/token-manager.ts#L198-L234","documentation":"TokenManager.refresh() requires the manager to be running (listener set by start()). Calling refresh before start() or after stop() throws. The guard prevents operating on a manager with no listener to receive the new token.","triggerScenarios":"Calling manager.refresh() before start(listener) has been invoked; after stop() has been called; or after the manager auto-stopped following a fatal non-retryable IDP error.","commonSituations":"A setInterval that calls refresh outliving the manager; teardown ordering where refresh fires after stop; test cleanup racing a scheduled refresh; calling refresh from application code without checking lifecycle.","solutions":["Guard the call with manager.isRunning() first.","Ensure start() has resolved before scheduling any manual refresh.","Cancel timers that drive refresh before calling stop()."],"exampleFix":"// before\nmanager.refresh();\n\n// after\nif (manager.isRunning()) {\n  await manager.refresh();\n}","handlingStrategy":"validation","validationCode":"if (manager.isRunning()) {\n  await manager.refresh();\n} else {\n  // start it first, or skip\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair manual refresh calls with an isRunning() check.","Cancel timers driving refresh before calling stop().","Treat start()/stop() as the lifecycle boundary; do not call refresh across it."],"tags":["lifecycle","token-manager","auth","validation"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}