mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
e00eb50db3
commit
ad09c138b9
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user