Fix Codex OAuth auth being cleared during preserve-mode takeover

When "preserve official auth on switch" is enabled, proxy takeover routes
the PROXY_MANAGED placeholder into config.toml's experimental_bearer_token
and leaves auth.json (the ChatGPT OAuth login) untouched. Takeover detection
only inspected auth.json's OPENAI_API_KEY, so it never recognized this state
and returned a false negative, which led downstream paths to clobber the
preserved OAuth login.

- Detection: is_codex_live_taken_over now also matches a config.toml
  experimental_bearer_token equal to the placeholder, fixing detect/cleanup/
  restore/startup-recovery in one place.
- Cleanup: remove the config.toml bearer token only when it equals the
  placeholder (new remove_codex_experimental_bearer_token_if predicate), so a
  real third-party key is never stripped.
- Write: under preservation, the None-provider takeover path writes only
  config.toml and keeps auth.json intact, matching the provider path.
- Settings: rename the section to "Codex App Enhancements" and reword the
  description across all four locales.
- Add tests covering OAuth preservation on takeover and placeholder-only
  cleanup.
This commit is contained in:
Jason
2026-05-30 22:56:36 +08:00
parent 60a9b330e5
commit c9cadd6e09
7 changed files with 224 additions and 25 deletions
+24 -3
View File
@@ -739,7 +739,10 @@ fn set_codex_experimental_bearer_token(config_text: &str, token: &str) -> Result
Ok(doc.to_string())
}
fn remove_codex_experimental_bearer_token(config_text: &str) -> Result<String, AppError> {
pub fn remove_codex_experimental_bearer_token_if(
config_text: &str,
predicate: impl Fn(&str) -> bool,
) -> Result<String, AppError> {
if config_text.trim().is_empty() || !config_text.contains("experimental_bearer_token") {
return Ok(config_text.to_string());
}
@@ -755,14 +758,32 @@ fn remove_codex_experimental_bearer_token(config_text: &str) -> Result<String, A
.and_then(|table| table.get_mut(provider_id.as_str()))
.and_then(|item| item.as_table_mut())
{
provider_table.remove("experimental_bearer_token");
let should_remove = provider_table
.get("experimental_bearer_token")
.and_then(|item| item.as_str())
.map(str::trim)
.is_some_and(&predicate);
if should_remove {
provider_table.remove("experimental_bearer_token");
}
}
}
doc.as_table_mut().remove("experimental_bearer_token");
let should_remove_top_level = doc
.get("experimental_bearer_token")
.and_then(|item| item.as_str())
.map(str::trim)
.is_some_and(&predicate);
if should_remove_top_level {
doc.as_table_mut().remove("experimental_bearer_token");
}
Ok(doc.to_string())
}
fn remove_codex_experimental_bearer_token(config_text: &str) -> Result<String, AppError> {
remove_codex_experimental_bearer_token_if(config_text, |_| true)
}
/// Read the current Codex live settings as a `{ auth, config }` object.
///
/// Missing `auth.json` collapses to `{}` so a config-only third-party install