Fix codex CLI auth: use codex login --with-api-key for credentials

Codex CLI no longer reads OPENAI_API_KEY from the environment at
runtime. It requires `codex login --with-api-key` to store credentials
via its auth system. Add a login step after ensure_cli for codex.

Also pass API keys via exec_command's env_vars parameter as belt-and-
suspenders alongside the env file, to avoid fs-to-process race conditions
in the Daytona sandbox.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-08 19:22:41 -04:00
parent dd9bed01e5
commit f6284026b1

View file

@ -486,21 +486,50 @@ impl CodergenBackend for AgentCliBackend {
let _ = tokio::fs::write(stage_dir.join("provider_used.json"), json).await;
}
// Forward provider API key so the CLI tool can authenticate.
// Written to a temp file and sourced, since Daytona's exec API doesn't
// support env vars and inline export would leak keys in logs.
let mut env_lines: Vec<String> = vec![
// Ensure npm-installed CLIs in ~/.local/bin are on PATH
"export PATH=\"$HOME/.local/bin:$PATH\"".to_string(),
];
env_lines.extend(provider.api_key_env_vars().iter().filter_map(|name| {
std::env::var(name)
.ok()
.map(|val| format!("export {name}='{}'", shell_escape(&val)))
}));
for (name, val) in &self.env {
env_lines.push(format!("export {name}='{}'", shell_escape(val)));
// Forward provider API key and custom env vars so the CLI tool can authenticate.
// Build a HashMap to pass via exec_command's env_vars parameter — this
// prepends `export` statements directly into the base64-encoded command,
// avoiding filesystem-to-process race conditions that can occur when
// writing an env file via the fs API and sourcing it via the process API.
let mut launch_env: HashMap<String, String> = HashMap::new();
for name in provider.api_key_env_vars() {
if let Ok(val) = std::env::var(name) {
launch_env.insert((*name).to_string(), val);
}
}
for (name, val) in &self.env {
launch_env.insert(name.clone(), val.clone());
}
// Codex CLI requires `codex login --with-api-key` to store credentials;
// it does not read OPENAI_API_KEY from the environment at runtime.
if cli == AgentCli::Codex {
if let Some(api_key) = launch_env.get("OPENAI_API_KEY") {
let login_cmd = format!(
"PATH=\"$HOME/.local/bin:$PATH\" echo '{}' | codex login --with-api-key",
shell_escape(api_key)
);
let login_result = sandbox
.exec_command(&login_cmd, 30_000, None, None, None)
.await
.map_err(|e| ArcError::handler(format!("codex login failed: {e}")))?;
if login_result.exit_code != 0 {
tracing::warn!(
exit_code = login_result.exit_code,
"codex login --with-api-key failed: {}",
login_result.stderr
);
}
}
}
// Also write env file as fallback for commands that source it (e.g. ensure_cli PATH)
let mut env_lines: Vec<String> = vec!["export PATH=\"$HOME/.local/bin:$PATH\"".to_string()];
env_lines.extend(
launch_env
.iter()
.map(|(k, v)| format!("export {k}='{}'", shell_escape(v))),
);
{
sandbox
.write_file(&env_path, &env_lines.join("\n"))
@ -524,8 +553,13 @@ impl CodergenBackend for AgentCliBackend {
"SID=$(command -v setsid || true)\n$SID sh -c '{inner_command} > {stdout_path} 2>{stderr_path}; echo $? > {exit_code_path}' </dev/null >/dev/null 2>&1 &\necho $!"
);
let launch_start = std::time::Instant::now();
let launch_env_ref = if launch_env.is_empty() {
None
} else {
Some(&launch_env)
};
let launch_result = sandbox
.exec_command(&bg_command, 30_000, None, None, None)
.exec_command(&bg_command, 30_000, None, launch_env_ref, None)
.await
.map_err(|e| ArcError::handler(format!("Failed to launch CLI command: {e}")))?;
let pid = launch_result.stdout.trim();