mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
fix(rmcp): refresh expired OAuth tokens before startup (#26482)
## Why Codex persists OAuth expiry as an absolute `expires_at`, then reconstructs RMCP’s relative `expires_in` when credentials are loaded. For an already-expired token, Codex reconstructed `expires_in` as missing. [RMCP 0.15 treated a missing `expires_in` as zero when a refresh token was present](https://github.com/modelcontextprotocol/rust-sdk/blob/9cfc905a9ef17c8bba6748dc0a9bdd2452681733/crates/rmcp/src/transport/auth.rs#L704-L723), so this still triggered a refresh. [RMCP 1.7 treats missing expiry information as unknown and uses the access token as-is](https://github.com/modelcontextprotocol/rust-sdk/blob/3529c3675ff64db805bd947ca6ece6090809e43d/crates/rmcp/src/transport/auth.rs#L1233-L1265), causing the stale token to be sent during `initialize`. ## What changed - Represent a known-expired persisted token as `expires_in = 0`, preserving `None` for genuinely unknown expiry. - Add Streamable HTTP coverage requiring the token to refresh before the startup handshake. ## Validation - The new regression test fails on RMCP 1.7 before the fix and passes afterward. - The same scenario passes on the commit immediately before the RMCP 1.7 update, using RMCP 0.15. - `just test -p codex-rmcp-client` (63 passed).
This commit is contained in:
@@ -113,7 +113,13 @@ fn refresh_expires_in_from_timestamp(tokens: &mut StoredOAuthTokens) {
|
||||
tokens.token_response.0.set_expires_in(Some(&duration));
|
||||
}
|
||||
None => {
|
||||
tokens.token_response.0.set_expires_in(None);
|
||||
// RMCP treats a missing expiry as unknown and uses the access token
|
||||
// as-is. Treat a known-expired timestamp as an explicit zero so
|
||||
// startup refreshes the token before the first request.
|
||||
tokens
|
||||
.token_response
|
||||
.0
|
||||
.set_expires_in(Some(&Duration::ZERO));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,7 +836,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_expires_in_from_timestamp_clears_expired_tokens() {
|
||||
fn refresh_expires_in_from_timestamp_marks_expired_tokens() {
|
||||
let mut tokens = sample_tokens();
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -843,7 +849,7 @@ mod tests {
|
||||
|
||||
super::refresh_expires_in_from_timestamp(&mut tokens);
|
||||
|
||||
assert!(tokens.token_response.0.expires_in().is_none());
|
||||
assert_eq!(tokens.token_response.0.expires_in(), Some(Duration::ZERO));
|
||||
}
|
||||
|
||||
fn assert_tokens_match_without_expiry(
|
||||
|
||||
Reference in New Issue
Block a user