Skip to content

fix: apply expiry buffer before reusing cached tokens#331

Open
aaguiarz wants to merge 5 commits intomainfrom
fix/issue-330-token-expiry-buffer
Open

fix: apply expiry buffer before reusing cached tokens#331
aaguiarz wants to merge 5 commits intomainfrom
fix/issue-330-token-expiry-buffer

Conversation

@aaguiarz
Copy link
Member

@aaguiarz aaguiarz commented Feb 16, 2026

Summary

Applies the SDK token-expiry threshold/jitter when deciding whether a cached client-credentials token can be reused.

Changes

  • Added regression test proving a short-lived token (expires_in: 120) should be refreshed on the next call
  • Updated getAccessTokenValue() to treat tokens within the configured threshold/jitter window as expired
  • Reused SdkConstants.TokenExpiryThresholdBufferInSec and SdkConstants.TokenExpiryJitterInSec

Potential Breaking Change

  • Token reuse behavior changes: near-expiry tokens are now proactively refreshed.
  • Deployments using short-lived tokens may see more token endpoint traffic and different timing/performance characteristics.

Fixes #330

Summary by CodeRabbit

  • New Features

    • Enhanced token refresh mechanism with expiry safety buffer to prevent using expired credentials and improve reliability.
  • Tests

    • Added test coverage for token refresh behavior when tokens approach expiration.

Copilot AI review requested due to automatic review settings February 16, 2026 18:54
@aaguiarz aaguiarz requested a review from a team as a code owner February 16, 2026 18:54
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Implements a token expiry safety buffer mechanism in the credentials module. A random buffer value is computed from SDK constants upon token refresh and applied to token expiration checks, causing cached tokens within the buffer window to be proactively refreshed rather than reused.

Changes

Cohort / File(s) Summary
Token Expiry Buffer Implementation
credentials/credentials.ts
Adds private field accessTokenExpiryBufferInMs, imports SdkConstants, and modifies token validity checks to account for expiry buffer. Buffer is computed with random jitter on token refresh and subtracted from expiration time when determining token reusability.
Test Coverage
tests/credentials.test.ts
Introduces test verifying that cached tokens nearing expiration (within the configured buffer threshold) are refreshed instead of reused. Mocks Math.random and token endpoint to validate two sequential token retrievals.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: applying an expiry buffer before reusing cached tokens, which directly addresses the PR's core objective.
Linked Issues check ✅ Passed The code changes implement all requirements from issue #330: adds token expiry buffer mechanism, applies TokenExpiryThresholdBufferInSec and jitter to token validity checks, and includes regression test.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing token expiry buffer logic as specified in issue #330; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-330-token-expiry-buffer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot
Copy link

dosubot bot commented Feb 16, 2026

Related Documentation

Checked 8 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates client-credentials token caching so cached access tokens are treated as expired when they fall within the SDK’s configured expiry threshold + jitter window, reducing the chance of using near-expiry tokens on outbound requests.

Changes:

  • Updated Credentials.getAccessTokenValue() to apply the SDK token-expiry threshold/jitter before reusing a cached token.
  • Added a regression test ensuring a short-lived token is refreshed on the next call.
  • Reused existing expiry-buffer/jitter constants from SdkConstants.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
credentials/credentials.ts Applies expiry threshold+jitter when deciding whether to reuse a cached client-credentials token.
tests/credentials.test.ts Adds a test asserting short-lived cached tokens are proactively refreshed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 141 to 148
const tokenExpiryBufferInMs = (
SdkConstants.TokenExpiryThresholdBufferInSec +
(Math.random() * SdkConstants.TokenExpiryJitterInSec)
) * 1000;
const tokenIsValid = !this.accessTokenExpiryDate || (
this.accessTokenExpiryDate.getTime() - Date.now() > tokenExpiryBufferInMs
);
if (this.accessToken && tokenIsValid) {
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokenExpiryBufferInMs uses Math.random() on every call to getAccessTokenValue(), which can make a cached token flip between “valid” and “expired” across successive calls and cause unnecessary token refreshes. Consider computing/storing the randomized buffer once per fetched token (or pre-adjusting accessTokenExpiryDate when the token is received) so the validity decision is stable for that token.

Copilot uses AI. Check for mistakes.
import { TelemetryCounters } from "../telemetry/counters";
import { TelemetryConfiguration } from "../telemetry/configuration";
import { randomUUID } from "crypto";
import SdkConstants from "../constants";
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file header states it is auto-generated (“DO NOT EDIT”), but this change introduces a new import. If this file is regenerated as part of the build/release process, these edits may be overwritten; consider updating the generator template/source or adjusting the generation settings/header so the change persists reliably.

Copilot uses AI. Check for mistakes.
Comment on lines 548 to 551
.reply(200, {
access_token: "short-lived-token",
expires_in: 120,
})
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hardcodes expires_in: 120, which is only “close to expiration” relative to the current buffer/jitter constants. To keep the test resilient if TokenExpiryThresholdBufferInSec/TokenExpiryJitterInSec change, consider stubbing Math.random() and deriving expires_in from the exported constants so the token is guaranteed to fall within the buffer window.

Copilot uses AI. Check for mistakes.
@aaguiarz
Copy link
Member Author

Addressed the actionable review comments in commit 26a7d7b.

Implemented:

  • made token-expiry jitter stable per cached token by storing a randomized buffer when the token is fetched (accessTokenExpiryBufferInMs), instead of recomputing randomness on every getAccessTokenValue() call
  • hardened the regression test by stubbing Math.random() and deriving expires_in from SdkConstants.TokenExpiryThresholdBufferInSec so the test remains valid if constants change

Validated locally:

  • npm test -- tests/credentials.test.ts -t "close to expiration"

On the autogenerated-file/header comment: agreed in principle; this PR is scoped to the runtime bugfix/tests, and generator-template alignment can be tracked separately.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@credentials/credentials.ts`:
- Around line 141-150: The const tokenIsValid declared inside the
CredentialsMethod.ClientCredentials case is not scoped because case clauses fall
through; wrap the entire case body in braces to limit tokenIsValid's scope.
Specifically, in the switch handling CredentialsMethod.ClientCredentials, add a
block { ... } around the logic that computes tokenIsValid (which references
accessTokenExpiryDate and accessTokenExpiryBufferInMs), checks this.accessToken
and tokenIsValid, and returns this.accessToken or calls
this.refreshAccessToken(); this confines the const to that case only.
🧹 Nitpick comments (1)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@credentials/credentials.ts`:
- Around line 141-150: The const tokenIsValid declared inside the
CredentialsMethod.ClientCredentials case is not scoped because case clauses fall
through; wrap the entire case body in braces to limit tokenIsValid's scope.
Specifically, in the switch handling CredentialsMethod.ClientCredentials, add a
block { ... } around the logic that computes tokenIsValid (which references
accessTokenExpiryDate and accessTokenExpiryBufferInMs), checks this.accessToken
and tokenIsValid, and returns this.accessToken or calls
this.refreshAccessToken(); this confines the const to that case only.
credentials/credentials.ts (1)

141-150: Wrap case body in a block to scope the const declaration.

Biome correctly flags this: const tokenIsValid declared in a case clause without a block is technically accessible from other case branches due to fall-through semantics. Wrap in braces to restrict scope.

♻️ Proposed fix
     case CredentialsMethod.ClientCredentials:
-      const tokenIsValid = !this.accessTokenExpiryDate || (
-        this.accessTokenExpiryDate.getTime() - Date.now() > this.accessTokenExpiryBufferInMs
-      );
-      if (this.accessToken && tokenIsValid) {
-        return this.accessToken;
-      }
-
-      return this.refreshAccessToken();
+    {
+      const tokenIsValid = !this.accessTokenExpiryDate || (
+        this.accessTokenExpiryDate.getTime() - Date.now() > this.accessTokenExpiryBufferInMs
+      );
+      if (this.accessToken && tokenIsValid) {
+        return this.accessToken;
+      }
+
+      return this.refreshAccessToken();
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@credentials/credentials.ts` around lines 141 - 150, The const tokenIsValid
declared inside the CredentialsMethod.ClientCredentials case is not scoped
because case clauses fall through; wrap the entire case body in braces to limit
tokenIsValid's scope. Specifically, in the switch handling
CredentialsMethod.ClientCredentials, add a block { ... } around the logic that
computes tokenIsValid (which references accessTokenExpiryDate and
accessTokenExpiryBufferInMs), checks this.accessToken and tokenIsValid, and
returns this.accessToken or calls this.refreshAccessToken(); this confines the
const to that case only.

@aaguiarz
Copy link
Member Author

aaguiarz commented Feb 16, 2026

Addressed the failing test in commit fbc241b.

Root cause:

  • #331 introduces proactive near-expiry refresh, and the existing cache test used the default token fixture (expires_in: 300), which is now intentionally within the refresh window.

Fix applied:

  • Updated tests/index.test.ts test should cache the bearer token and not issue a network call to get the token at the second request to use a long-lived token (expires_in: 3600) for the initial exchange.
  • This keeps the test focused on caching semantics rather than near-expiry refresh behavior.

Validated locally:

  • npm test -- tests/index.test.ts -t "should cache the bearer token and not issue a network call to get the token at the second request"

@codecov-commenter
Copy link

codecov-commenter commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.70%. Comparing base (a7090e2) to head (4014b7e).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #331      +/-   ##
==========================================
+ Coverage   89.67%   89.70%   +0.02%     
==========================================
  Files          25       25              
  Lines        1492     1496       +4     
  Branches      279      280       +1     
==========================================
+ Hits         1338     1342       +4     
  Misses         94       94              
  Partials       60       60              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aaguiarz
Copy link
Member Author

Followed up on an additional coderabbitai suggestion that still made sense.

Implemented in commit 4014b7e:

  • wrapped the CredentialsMethod.ClientCredentials switch case body in braces in getAccessTokenValue()
  • this scopes const tokenIsValid to that case and avoids switch-case lexical leakage warnings

Validated with:

  • npm test -- tests/credentials.test.ts -t "close to expiration"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: apply token expiry buffer before reusing cached access token

2 participants