Race condition in OAuth token refresh causes "refresh token was already used" error when multiple app-server instances run concurrently

Resolved 💬 4 comments Opened Feb 1, 2026 by xiaojay30 Closed Feb 2, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex is running?

0.93.0

What subscription do you have?

team

Which model were you using?

gpt-5.2

What platform is your computer?

Darwin 22.6.0 x86_64 i386

What terminal emulator and version are you using (if applicable)?

_No response_

What issue are you seeing?

When multiple codex app-server instances are running simultaneously (e.g., multiple VS Code/Windsurf windows), a
race condition can occur during OAuth token refresh, resulting in the error:

Your access token could not be refreshed because your refresh token was already used. Please log out and sign in
again.

What steps can reproduce the bug?

Root Cause Analysis

The token refresh mechanism in codex-rs/core/src/auth.rs lacks cross-process synchronization:

  1. No file lock when refreshing tokens (auth.rs:1251-1267)
  2. No distributed lock or mutex across processes
  3. No check if another process has already refreshed the token before calling the refresh API

Since OAuth uses Refresh Token Rotation (each refresh token is single-use), when multiple processes detect token
staleness simultaneously and race to refresh:

T0: app-server-1 detects token needs refresh
T0: app-server-2 also detects token needs refresh
T1: app-server-1 calls refresh API → gets new refresh_token_v2, writes to disk
T2: app-server-2 calls refresh API with stale refresh_token_v1
T3: app-server-2 receives "refresh_token_reused" error ❌

What is the expected behavior?

_No response_

Additional information

Steps to Reproduce

  1. Open multiple Windsurf/VS Code windows (each spawns a separate app-server process)
  2. Wait until token refresh is due (~8 days after last refresh, per TOKEN_REFRESH_INTERVAL)
  3. Trigger API calls from multiple windows around the same time (e.g., restart IDE, or use a tool like CodexBar

that polls via RPC)

Environment

  • macOS (likely affects all platforms)
  • Multiple codex app-server --analytics-default-enabled processes running

Suggested Fix

Add file-based locking before token refresh:

// Before refreshing, acquire a file lock
let lock_path = storage.path().with_extension("refresh.lock");
let lock = FileLock::acquire(&lock_path)?;

// Re-check if token still needs refresh (another process may have refreshed it)
let current_tokens = storage.load()?;
if !needs_refresh(&current_tokens) {
return Ok(()); // Already refreshed by another process
}

// Proceed with refresh
let refresh_response = try_refresh_token(refresh_token, client).await?;
update_tokens(storage, refresh_response)?;

Alternatively, consider using a single shared app-server process (singleton pattern) instead of spawning one per IDE
window.

View original on GitHub ↗

4 Comments

github-actions[bot] contributor · 6 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #9634

Powered by Codex Action

etraut-openai contributor · 6 months ago

Thanks, but I think there's a hole in your analysis that makes the diagnosis incorrect. Our OAuth refresh mechanism allows a refresh token to be used multiple times over a limited time window (on the order of an hour) before it is permanently invalidated. This accommodates network flakiness and other similar conditions. You're correct that the lack of a file lock can result in a race between Codex instances, but this race is mitigated by the refresh behavior on the server. In step T3 in your above analysis, app-server-2 would not receive a "refresh_token_reused" error in this situation. It would receive a proper access token / refresh token pair. One of the two instances would succeed in writing their updated token pair to the file.

We have received occasional reports of the "refresh token reused" error, so there is likely a bug lurking somewhere still. But I don't think the situation you've outlined above explains the problem. Let me know if you have any other theories or clues.

21888 · 6 months ago

+1

pjperez · 6 months ago

@etraut-openai seeing the same issue here, uploaded logs and left it in state if you need me to do further repros. Thread id 019c2aa3-984c-79b1-9a8c-11598c0b1e79