codex: remove create-api-key process env mutation

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Michael Fan
2026-03-26 15:16:43 -04:00
parent b54bfc88d5
commit 44d1bc54a5
4 changed files with 82 additions and 41 deletions

View File

@@ -127,6 +127,10 @@ impl CodexThread {
self.codex.session.set_dependency_env(values).await;
}
pub async fn dependency_env(&self) -> HashMap<String, String> {
self.codex.session.dependency_env().await
}
pub(crate) fn subscribe_status(&self) -> watch::Receiver<AgentStatus> {
self.codex.agent_status.clone()
}

View File

@@ -3133,6 +3133,18 @@ impl App {
};
let _ = result_tx.send(result);
}
AppEvent::GetDependencyEnv {
thread_id,
result_tx,
} => {
let result = match self.server.get_thread(thread_id).await {
Ok(thread) => Ok(thread.dependency_env().await),
Err(err) => Err(format!(
"failed to load Codex thread {thread_id} for dependency env read: {err}"
)),
};
let _ = result_tx.send(result);
}
AppEvent::PluginInstallAuthAdvance { refresh_connectors } => {
if refresh_connectors {
self.chat_widget.refresh_connectors(/*force_refetch*/ true);

View File

@@ -177,6 +177,12 @@ pub(crate) enum AppEvent {
result_tx: oneshot::Sender<Result<(), String>>,
},
/// Read the specified thread's dependency env override.
GetDependencyEnv {
thread_id: ThreadId,
result_tx: oneshot::Sender<Result<HashMap<String, String>, String>>,
},
/// Fetch plugin marketplace state for the provided working directory.
FetchPluginsList {
cwd: PathBuf,

View File

@@ -19,45 +19,76 @@ use crate::history_cell::PlainHistoryCell;
impl ChatWidget {
pub(crate) fn start_create_api_key(&mut self) {
match start_create_api_key_command(self.thread_id(), self.app_event_tx.clone()) {
Ok(start_message) => {
self.add_to_history(start_message);
self.request_redraw();
}
Err(err) => {
self.add_error_message(err);
}
}
let Some(thread_id) = self.thread_id() else {
self.add_error_message("No active Codex thread for API key creation.".to_string());
return;
};
let app_event_tx = self.app_event_tx.clone();
tokio::spawn(async move {
let cell = start_create_api_key_command(thread_id, app_event_tx.clone()).await;
app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(cell)));
});
}
}
fn start_create_api_key_command(
thread_id: Option<ThreadId>,
async fn start_create_api_key_command(
thread_id: ThreadId,
app_event_tx: AppEventSender,
) -> Result<PlainHistoryCell, String> {
let thread_id =
thread_id.ok_or_else(|| "No active Codex thread for API key creation.".to_string())?;
if read_openai_api_key_from_env().is_some() {
return Ok(existing_shell_api_key_message());
) -> PlainHistoryCell {
match is_openai_api_key_set_in_session(thread_id, app_event_tx.clone()).await {
Ok(true) => return existing_api_key_message(),
Ok(false) => {}
Err(err) => {
return history_cell::new_error_event(format!(
"Failed to check API key environment: {err}"
));
}
}
let session = start_create_api_key_flow()
.map_err(|err| format!("Failed to start API key creation: {err}"))?;
let session = match start_create_api_key_flow() {
Ok(session) => session,
Err(err) => {
return history_cell::new_error_event(format!(
"Failed to start API key creation: {err}"
));
}
};
let browser_opened = session.open_browser();
let start_message =
continue_in_browser_message(session.auth_url(), session.callback_port(), browser_opened);
app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(start_message)));
let app_event_tx_for_task = app_event_tx;
tokio::spawn(async move {
let cell = complete_command(session, thread_id, app_event_tx_for_task.clone()).await;
app_event_tx_for_task.send(AppEvent::InsertHistoryCell(Box::new(cell)));
});
Ok(start_message)
complete_command(session, thread_id, app_event_tx).await
}
fn existing_shell_api_key_message() -> PlainHistoryCell {
async fn is_openai_api_key_set_in_session(
thread_id: ThreadId,
app_event_tx: AppEventSender,
) -> Result<bool, String> {
if read_openai_api_key_from_env().is_some() {
return Ok(true);
}
let (result_tx, result_rx) = oneshot::channel();
app_event_tx.send(AppEvent::GetDependencyEnv {
thread_id,
result_tx,
});
let dependency_env = match result_rx.await {
Ok(result) => result?,
Err(err) => {
return Err(format!(
"dependency env read response channel closed before completion: {err}"
));
}
};
Ok(dependency_env.contains_key(OPENAI_API_KEY_ENV_VAR))
}
fn existing_api_key_message() -> PlainHistoryCell {
history_cell::new_info_event(
format!(
"{OPENAI_API_KEY_ENV_VAR} is already set in this Codex session; skipping API key creation."
@@ -144,8 +175,6 @@ async fn apply_api_key_to_current_session(
thread_id: ThreadId,
app_event_tx: AppEventSender,
) -> Result<(), String> {
set_current_process_api_key(api_key);
let (result_tx, result_rx) = oneshot::channel();
app_event_tx.send(AppEvent::SetDependencyEnv {
thread_id,
@@ -161,16 +190,6 @@ async fn apply_api_key_to_current_session(
}
}
fn set_current_process_api_key(api_key: &str) {
// SAFETY: `/create-api-key` intentionally mutates process-global environment so the running
// Codex session can observe `OPENAI_API_KEY` immediately. This is scoped to a single
// user-triggered command, and spawned tool environments are updated separately through the
// session dependency env override.
unsafe {
std::env::set_var(OPENAI_API_KEY_ENV_VAR, api_key);
}
}
fn success_cell(
provisioned: &CreatedApiKey,
copy_result: Result<(), String>,
@@ -272,8 +291,8 @@ mod tests {
}
#[test]
fn existing_shell_api_key_message_mentions_openai_api_key() {
let cell = existing_shell_api_key_message();
fn existing_api_key_message_mentions_openai_api_key() {
let cell = existing_api_key_message();
assert_eq!(
render_cell(&cell),