Fixed status output to use auth information from AuthManager (#6529)

This PR addresses https://github.com/openai/codex/issues/6360. The root
problem is that the TUI was directly loading the `auth.json` file to
access the auth information. It should instead be using the AuthManager,
which records the current auth information. The `auth.json` file can be
overwritten at any time by other instances of the CLI or extension, so
its information can be out of sync with the current instance. The
`/status` command should always report the auth information associated
with the current instance.

An alternative fix for this bug was submitted by @chojs23 in [this
PR](https://github.com/openai/codex/pull/6495). That approach was only a
partial fix.
This commit is contained in:
Eric Traut
2025-11-12 12:26:50 -06:00
committed by GitHub
Unverified
parent e00eb50db3
commit ad09c138b9
5 changed files with 78 additions and 22 deletions
+12 -1
View File
@@ -227,6 +227,14 @@ impl CodexAuth {
})
}
/// Raw plan string from the ID token (including unknown/new plan types).
pub fn raw_plan_type(&self) -> Option<String> {
self.get_plan_type().map(|plan| match plan {
InternalPlanType::Known(k) => format!("{k:?}"),
InternalPlanType::Unknown(raw) => raw,
})
}
/// Raw internal plan value from the ID token.
/// Exposes the underlying `token_data::PlanType` without mapping it to the
/// public `AccountPlanType`. Use this when downstream code needs to inspect
@@ -335,7 +343,10 @@ pub fn save_auth(
}
/// Load CLI auth data using the configured credential store backend.
/// Returns `None` when no credentials are stored.
/// Returns `None` when no credentials are stored. This function is
/// provided only for tests. Production code should not directly load
/// from the auth.json storage. It should use the AuthManager abstraction
/// instead.
pub fn load_auth_dot_json(
codex_home: &Path,
auth_credentials_store_mode: AuthCredentialsStoreMode,
+1
View File
@@ -1709,6 +1709,7 @@ impl ChatWidget {
};
self.add_to_history(crate::status::new_status_output(
&self.config,
self.auth_manager.as_ref(),
total_usage,
context_usage,
&self.conversation_id,
+5 -1
View File
@@ -33,6 +33,7 @@ use super::rate_limits::format_status_limit_summary;
use super::rate_limits::render_status_limit_progress_bar;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_lines;
use codex_core::AuthManager;
#[derive(Debug, Clone)]
struct StatusContextWindowData {
@@ -65,6 +66,7 @@ struct StatusHistoryCell {
pub(crate) fn new_status_output(
config: &Config,
auth_manager: &AuthManager,
total_usage: &TokenUsage,
context_usage: Option<&TokenUsage>,
session_id: &Option<ConversationId>,
@@ -74,6 +76,7 @@ pub(crate) fn new_status_output(
let command = PlainHistoryCell::new(vec!["/status".magenta().into()]);
let card = StatusHistoryCell::new(
config,
auth_manager,
total_usage,
context_usage,
session_id,
@@ -87,6 +90,7 @@ pub(crate) fn new_status_output(
impl StatusHistoryCell {
fn new(
config: &Config,
auth_manager: &AuthManager,
total_usage: &TokenUsage,
context_usage: Option<&TokenUsage>,
session_id: &Option<ConversationId>,
@@ -106,7 +110,7 @@ impl StatusHistoryCell {
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write".to_string(),
};
let agents_summary = compose_agents_summary(config);
let account = compose_account_display(config);
let account = compose_account_display(auth_manager);
let session_id = session_id.as_ref().map(std::string::ToString::to_string);
let context_window = config.model_context_window.and_then(|window| {
context_usage.map(|usage| StatusContextWindowData {
+11 -17
View File
@@ -2,7 +2,8 @@ use crate::exec_command::relativize_to_home;
use crate::text_formatting;
use chrono::DateTime;
use chrono::Local;
use codex_core::auth::load_auth_dot_json;
use codex_app_server_protocol::AuthMode;
use codex_core::AuthManager;
use codex_core::config::Config;
use codex_core::project_doc::discover_project_doc_paths;
use std::path::Path;
@@ -82,24 +83,17 @@ pub(crate) fn compose_agents_summary(config: &Config) -> String {
}
}
pub(crate) fn compose_account_display(config: &Config) -> Option<StatusAccountDisplay> {
let auth =
load_auth_dot_json(&config.codex_home, config.cli_auth_credentials_store_mode).ok()??;
pub(crate) fn compose_account_display(auth_manager: &AuthManager) -> Option<StatusAccountDisplay> {
let auth = auth_manager.auth()?;
if let Some(tokens) = auth.tokens.as_ref() {
let info = &tokens.id_token;
let email = info.email.clone();
let plan = info.get_chatgpt_plan_type().as_deref().map(title_case);
return Some(StatusAccountDisplay::ChatGpt { email, plan });
match auth.mode {
AuthMode::ChatGPT => {
let email = auth.get_account_email();
let plan = auth.raw_plan_type().map(|plan| title_case(plan.as_str()));
Some(StatusAccountDisplay::ChatGpt { email, plan })
}
AuthMode::ApiKey => Some(StatusAccountDisplay::ApiKey),
}
if let Some(key) = auth.openai_api_key
&& !key.is_empty()
{
return Some(StatusAccountDisplay::ApiKey);
}
None
}
pub(crate) fn format_tokens_compact(value: i64) -> String {
+49 -3
View File
@@ -4,6 +4,7 @@ use crate::history_cell::HistoryCell;
use chrono::Duration as ChronoDuration;
use chrono::TimeZone;
use chrono::Utc;
use codex_core::AuthManager;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConfigToml;
@@ -27,6 +28,14 @@ fn test_config(temp_home: &TempDir) -> Config {
.expect("load config")
}
fn test_auth_manager(config: &Config) -> AuthManager {
AuthManager::new(
config.codex_home.clone(),
false,
config.cli_auth_credentials_store_mode,
)
}
fn render_lines(lines: &[Line<'static>]) -> Vec<String> {
lines
.iter()
@@ -85,6 +94,7 @@ fn status_snapshot_includes_reasoning_details() {
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
@@ -113,6 +123,7 @@ fn status_snapshot_includes_reasoning_details() {
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
@@ -137,6 +148,7 @@ fn status_snapshot_includes_monthly_limit() {
config.model_provider_id = "openai".to_string();
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 800,
cached_input_tokens: 0,
@@ -161,6 +173,7 @@ fn status_snapshot_includes_monthly_limit() {
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
@@ -184,6 +197,7 @@ fn status_card_token_usage_excludes_cached_tokens() {
config.model = "gpt-5-codex".to_string();
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
@@ -197,7 +211,15 @@ fn status_card_token_usage_excludes_cached_tokens() {
.single()
.expect("timestamp");
let composite = new_status_output(&config, &usage, Some(&usage), &None, None, now);
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
None,
now,
);
let rendered = render_lines(&composite.display_lines(120));
assert!(
@@ -216,6 +238,7 @@ fn status_snapshot_truncates_in_narrow_terminal() {
config.model_reasoning_summary = ReasoningSummary::Detailed;
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
@@ -240,6 +263,7 @@ fn status_snapshot_truncates_in_narrow_terminal() {
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
@@ -264,6 +288,7 @@ fn status_snapshot_shows_missing_limits_message() {
config.model = "gpt-5-codex".to_string();
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
@@ -277,7 +302,15 @@ fn status_snapshot_shows_missing_limits_message() {
.single()
.expect("timestamp");
let composite = new_status_output(&config, &usage, Some(&usage), &None, None, now);
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
None,
now,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
if cfg!(windows) {
for line in &mut rendered_lines {
@@ -295,6 +328,7 @@ fn status_snapshot_shows_empty_limits_message() {
config.model = "gpt-5-codex".to_string();
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
@@ -315,6 +349,7 @@ fn status_snapshot_shows_empty_limits_message() {
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
@@ -338,6 +373,7 @@ fn status_snapshot_shows_stale_limits_message() {
config.model = "gpt-5-codex".to_string();
config.cwd = PathBuf::from("/workspace/tests");
let auth_manager = test_auth_manager(&config);
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
@@ -367,6 +403,7 @@ fn status_snapshot_shows_stale_limits_message() {
let composite = new_status_output(
&config,
&auth_manager,
&usage,
Some(&usage),
&None,
@@ -389,6 +426,7 @@ fn status_context_window_uses_last_usage() {
let mut config = test_config(&temp_home);
config.model_context_window = Some(272_000);
let auth_manager = test_auth_manager(&config);
let total_usage = TokenUsage {
input_tokens: 12_800,
cached_input_tokens: 0,
@@ -409,7 +447,15 @@ fn status_context_window_uses_last_usage() {
.single()
.expect("timestamp");
let composite = new_status_output(&config, &total_usage, Some(&last_usage), &None, None, now);
let composite = new_status_output(
&config,
&auth_manager,
&total_usage,
Some(&last_usage),
&None,
None,
now,
);
let rendered_lines = render_lines(&composite.display_lines(80));
let context_line = rendered_lines
.into_iter()