kimi: adopt peer-rotated credential on refresh 401 instead of clearing the login - #101
kimi: adopt peer-rotated credential on refresh 401 instead of clearing the login#101flaviomartil wants to merge 1 commit into
Conversation
The Kimi refresh token rotates on every refresh. When a peer process sharing the credential (for example the Kimi CLI) refreshes first, the proxy's copy is stale and the server rejects it with 401/403. The previous handling cleared the stored login outright, forcing a manual re-login even though the file on disk already held a valid, newer credential. Reload the credential from disk on 401/403 and retry once with the newer refresh token before clearing. Auth is only discarded when the disk holds no newer credential, or when the newer credential is also rejected.
There was a problem hiding this comment.
Pull request overview
This PR improves the Kimi OAuth refresh flow in the proxy to handle refresh-token rotation across multiple processes sharing the same credential file, avoiding unnecessary forced re-logins when a peer has already rotated the token.
Changes:
- Introduces a structured refresh error path to distinguish unauthorized refresh failures from other errors.
- On 401/403 refresh responses, reloads credentials from disk once and retries refresh if a newer refresh token is detected.
- Adds two unit tests covering peer-rotation adoption and the fallback behavior where auth is cleared only when no newer credential exists.
Suppressed comments (1)
src/providers/kimi/auth/manager.rs:309
std::env::set_var/remove_varare safe APIs; theunsafeblock inDropis unnecessary and may tripclippy::unnecessary_unsafe.
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match self.previous.take() {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| impl EnvGuard { | ||
| fn set(key: &'static str, value: &str) -> Self { | ||
| let previous = std::env::var_os(key); | ||
| unsafe { | ||
| std::env::set_var(key, value); | ||
| } | ||
| Self { key, previous } | ||
| } | ||
| } |
There was a problem hiding this comment.
This crate is on edition 2024 (see Cargo.toml), where std::env::set_var is an unsafe fn — the unsafe block is required and matches the existing EnvGuard pattern in src/config.rs tests. cargo clippy --lib --tests is clean.
|
Could you clarify the real-world scenario that requires this change? Claude Code Proxy stores its own Kimi credentials separately from the Kimi CLI, so I would not expect the CLI to rotate the proxy refresh token unless the credential files are explicitly symlinked or synchronized. Did you encounter this with shared credential files, multiple proxy instances, or concurrent proxy requests? A concrete reproduction would help me understand which case the fix needs to cover. |
|
Verified on my machine: the credential files are indeed separate (no symlinks) — the proxy uses The scenario is your option (b): multiple proxy instances sharing the proxy's own credential file. I run the proxy 24/7 as a Homebrew service on 127.0.0.1:18765. On Aug 2, while working on #99, I also ran dev/test builds from my fork that resolved the same real config/state dirs — proxy.log shows ~25 Since Kimi rotates the refresh token on every refresh (~15 min access tokens), whichever instance refreshed last invalidated the other's in-memory token; the stale instance's next refresh got 401 and the old code cleared the shared auth.json. Repro: run one instance as a service plus a second instance on the same config dir, wait for both to refresh — the one holding the stale token gets logged out. |
Problem
Kimi's OAuth refresh token rotates on every refresh. When another process that shares the same credential (most commonly the Kimi CLI itself, whose credential users seed into
kimi/auth.json) refreshes first, the proxy's copy of the refresh token is invalidated. The next proxy refresh then fails with 401/403.The current handling for 401/403 in
KimiAuthManager::refresh_nowclears the in-memory cache and deletes the stored credential (clear_auth()), forcing a manual re-login, even though the file on disk already holds a valid, newer credential written by the peer process.Fix
On 401/403 from the refresh endpoint, reload the credential from disk once:
This mirrors the peer-rotation adoption that
xaionaro-go/kimi-oauth-proxyimplements against the same OAuth backend.Tests
Two new unit tests in
kimi/auth/manager.rs, using the existingtest_httpmock server and theCCP_KIMI_OAUTH_HOSToverride:refresh_adopts_peer_rotated_credential_on_unauthorized: stale token gets 401, disk holds a newer credential, refresh succeeds with the peer token and persists the rotated result;refresh_clears_auth_only_when_disk_has_no_newer_credential: 401 with an unchanged file still clears the login, preserving the existing re-authentication behavior.cargo test --lib providers::kimi::auth: 7 passed.