Use OPENAI_API_KEY for api provisioning

Skip /api-provision when the current Codex process already inherited
OPENAI_API_KEY, and otherwise persist the provisioned key to .env under
OPENAI_API_KEY instead of CODEX_API_KEY.

Validation:
- cargo test -p codex-login
- cargo test -p codex-tui
- just fix -p codex-login
- just fix -p codex-tui
- just fmt

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Michael Fan
2026-03-23 21:59:08 +00:00
parent 6311965bc0
commit baafbe7026
4 changed files with 52 additions and 21 deletions

View File

@@ -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"
);
}

View File

@@ -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<ForcedLoginMethod>,
) -> Result<ApiProvisionStartMessage, String> {
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<AuthManager>,
) -> 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()

View File

@@ -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.

View File

@@ -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.