diff --git a/codex-rs/login/src/dotenv_api_key.rs b/codex-rs/login/src/dotenv_api_key.rs index 4242b62558..9ad9d18949 100644 --- a/codex-rs/login/src/dotenv_api_key.rs +++ b/codex-rs/login/src/dotenv_api_key.rs @@ -3,7 +3,7 @@ use std::io; use std::io::ErrorKind; use std::path::Path; -use codex_core::auth::CODEX_API_KEY_ENV_VAR; +use codex_core::auth::OPENAI_API_KEY_ENV_VAR; pub fn validate_dotenv_target(path: &Path) -> io::Result<()> { ensure_parent_dir(path)?; @@ -21,7 +21,7 @@ pub fn upsert_dotenv_api_key(path: &Path, api_key: &str) -> io::Result<()> { if api_key.contains(['\n', '\r']) { return Err(io::Error::new( ErrorKind::InvalidInput, - "CODEX_API_KEY must not contain newlines", + "OPENAI_API_KEY must not contain newlines", )); } @@ -37,9 +37,9 @@ pub fn upsert_dotenv_api_key(path: &Path, api_key: &str) -> io::Result<()> { let mut wrote_api_key = false; for segment in split_lines_preserving_terminators(&existing) { - if is_active_assignment_for(segment, CODEX_API_KEY_ENV_VAR) { + if is_active_assignment_for(segment, OPENAI_API_KEY_ENV_VAR) { if !wrote_api_key { - next.push_str(&format!("{CODEX_API_KEY_ENV_VAR}={api_key}\n")); + next.push_str(&format!("{OPENAI_API_KEY_ENV_VAR}={api_key}\n")); wrote_api_key = true; } continue; @@ -52,7 +52,7 @@ pub fn upsert_dotenv_api_key(path: &Path, api_key: &str) -> io::Result<()> { if !next.is_empty() && !next.ends_with('\n') { next.push('\n'); } - next.push_str(&format!("{CODEX_API_KEY_ENV_VAR}={api_key}\n")); + next.push_str(&format!("{OPENAI_API_KEY_ENV_VAR}={api_key}\n")); } std::fs::write(path, next) @@ -104,7 +104,7 @@ mod tests { upsert_dotenv_api_key(&dotenv_path, "sk-test-key").expect("write dotenv"); let written = std::fs::read_to_string(&dotenv_path).expect("read dotenv"); - assert_eq!(written, "CODEX_API_KEY=sk-test-key\n"); + assert_eq!(written, "OPENAI_API_KEY=sk-test-key\n"); } #[test] @@ -113,7 +113,7 @@ mod tests { let dotenv_path = temp_dir.path().join(".env"); std::fs::write( &dotenv_path, - "# comment\nCODEX_API_KEY=sk-old-1\nOTHER=value\nexport CODEX_API_KEY = sk-old-2\n", + "# comment\nOPENAI_API_KEY=sk-old-1\nOTHER=value\nexport OPENAI_API_KEY = sk-old-2\n", ) .expect("seed dotenv"); @@ -122,7 +122,7 @@ mod tests { let written = std::fs::read_to_string(&dotenv_path).expect("read dotenv"); assert_eq!( written, - "# comment\nCODEX_API_KEY=sk-new-key\nOTHER=value\n" + "# comment\nOPENAI_API_KEY=sk-new-key\nOTHER=value\n" ); } diff --git a/codex-rs/tui/src/api_provision.rs b/codex-rs/tui/src/api_provision.rs index b334dec702..f6b09b02cc 100644 --- a/codex-rs/tui/src/api_provision.rs +++ b/codex-rs/tui/src/api_provision.rs @@ -5,8 +5,9 @@ use std::sync::Arc; use codex_core::AuthManager; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::auth::login_with_api_key; +use codex_core::auth::read_openai_api_key_from_env; use codex_login::ApiProvisionOptions; -use codex_login::CODEX_API_KEY_ENV_VAR; +use codex_login::OPENAI_API_KEY_ENV_VAR; use codex_login::PendingApiProvisioning; use codex_login::ProvisionedApiKey; use codex_login::start_api_provisioning; @@ -31,14 +32,18 @@ pub(crate) fn start_command( cwd: PathBuf, forced_login_method: Option, ) -> Result { + if read_openai_api_key_from_env().is_some() { + return Ok(existing_shell_api_key_message()); + } + let dotenv_path = cwd.join(".env"); let start_hint = format!( - "Codex will save {CODEX_API_KEY_ENV_VAR} to {path} and hot-apply it here when allowed.", + "Codex will save {OPENAI_API_KEY_ENV_VAR} to {path} and hot-apply it here when allowed.", path = dotenv_path.display() ); validate_dotenv_target(&dotenv_path).map_err(|err| { format!( - "Unable to prepare {} for {CODEX_API_KEY_ENV_VAR}: {err}", + "Unable to prepare {} for {OPENAI_API_KEY_ENV_VAR}: {err}", dotenv_path.display(), ) })?; @@ -72,6 +77,17 @@ pub(crate) fn start_command( }) } +fn existing_shell_api_key_message() -> ApiProvisionStartMessage { + ApiProvisionStartMessage { + message: format!( + "{OPENAI_API_KEY_ENV_VAR} is already set in this Codex session; skipping API provisioning." + ), + hint: Some(format!( + "This Codex session already inherited {OPENAI_API_KEY_ENV_VAR} from its shell environment. Unset it and run /api-provision again if you want Codex to provision and save a different key." + )), + } +} + async fn complete_command( session: PendingApiProvisioning, dotenv_path: PathBuf, @@ -112,10 +128,9 @@ fn live_apply_api_key( auth_manager: Arc, ) -> LiveApplyOutcome { if matches!(forced_login_method, Some(ForcedLoginMethod::Chatgpt)) { - return LiveApplyOutcome::Skipped( - "Saved the key to .env, but left this session unchanged because ChatGPT login is required here." - .to_string(), - ); + return LiveApplyOutcome::Skipped(format!( + "Saved {OPENAI_API_KEY_ENV_VAR} to .env, but left this session unchanged because ChatGPT login is required here." + )); } match login_with_api_key(codex_home, api_key, AuthCredentialsStoreMode::Ephemeral) { @@ -147,14 +162,14 @@ fn success_cell( ), LiveApplyOutcome::Skipped(reason) => Some(reason), LiveApplyOutcome::Failed(err) => Some(format!( - "Saved the key to {}, but could not hot-apply it in this session: {err}", - dotenv_path.display() + "Saved {OPENAI_API_KEY_ENV_VAR} to {}, but could not hot-apply it in this session: {err}", + dotenv_path.display(), )), }; history_cell::new_info_event( format!( - "Provisioned an API key for {organization} / {project} and saved {CODEX_API_KEY_ENV_VAR} to {}.", + "Provisioned an API key for {organization} / {project} and saved {OPENAI_API_KEY_ENV_VAR} to {}.", dotenv_path.display() ), hint, @@ -206,7 +221,7 @@ mod tests { }, Path::new("/tmp/workspace/.env"), LiveApplyOutcome::Skipped( - "Saved the key to .env, but left this session unchanged because ChatGPT login is required here." + "Saved OPENAI_API_KEY to .env, but left this session unchanged because ChatGPT login is required here." .to_string(), ), ); @@ -214,6 +229,22 @@ mod tests { assert_snapshot!(render_cell(&cell)); } + #[test] + fn existing_shell_api_key_message_mentions_openai_api_key() { + let message = existing_shell_api_key_message(); + + assert_eq!( + message.message, + "OPENAI_API_KEY is already set in this Codex session; skipping API provisioning." + ); + assert_eq!( + message.hint, + Some( + "This Codex session already inherited OPENAI_API_KEY from its shell environment. Unset it and run /api-provision again if you want Codex to provision and save a different key.".to_string() + ) + ); + } + fn render_cell(cell: &PlainHistoryCell) -> String { cell.display_lines(120) .into_iter() diff --git a/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot.snap b/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot.snap index b594ca4fdb..839ce5259c 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot.snap @@ -3,4 +3,4 @@ source: tui/src/api_provision.rs assertion_line: 192 expression: render_cell(&cell) --- -• Provisioned an API key for Default Org / Default Project and saved CODEX_API_KEY to /tmp/workspace/.env. Updated this session to use the newly provisioned API key without touching auth.json. +• Provisioned an API key for Default Org / Default Project and saved OPENAI_API_KEY to /tmp/workspace/.env. Updated this session to use the newly provisioned API key without touching auth.json. diff --git a/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot_when_live_apply_is_skipped.snap b/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot_when_live_apply_is_skipped.snap index 96f8fe7ec8..6c7d9e3e43 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot_when_live_apply_is_skipped.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__api_provision__tests__success_cell_snapshot_when_live_apply_is_skipped.snap @@ -3,4 +3,4 @@ source: tui/src/api_provision.rs assertion_line: 214 expression: render_cell(&cell) --- -• Provisioned an API key for org-default / proj-default and saved CODEX_API_KEY to /tmp/workspace/.env. Saved the key to .env, but left this session unchanged because ChatGPT login is required here. +• Provisioned an API key for org-default / proj-default and saved OPENAI_API_KEY to /tmp/workspace/.env. Saved OPENAI_API_KEY to .env, but left this session unchanged because ChatGPT login is required here.