mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Add CLAUDE_ENV_FILE compatibility alias
This commit is contained in:
@@ -7,6 +7,7 @@ use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::shell_environment::CLAUDE_ENV_FILE_ENV_VAR;
|
||||
use codex_protocol::shell_environment::CODEX_ENV_FILE_ENV_VAR;
|
||||
use codex_protocol::shell_environment::CODEX_THREAD_ID_ENV_VAR;
|
||||
use tempfile::TempPath;
|
||||
@@ -52,11 +53,9 @@ impl ShellEnvFile {
|
||||
}
|
||||
|
||||
pub(crate) fn insert_path_into_env(&self, env: &mut HashMap<String, String>) {
|
||||
insert_env_var(
|
||||
env,
|
||||
CODEX_ENV_FILE_ENV_VAR.to_string(),
|
||||
self.path().to_string_lossy().to_string(),
|
||||
);
|
||||
let path = self.path().to_string_lossy().to_string();
|
||||
insert_env_var(env, CODEX_ENV_FILE_ENV_VAR.to_string(), path.clone());
|
||||
insert_env_var(env, CLAUDE_ENV_FILE_ENV_VAR.to_string(), path);
|
||||
}
|
||||
|
||||
/// Sources the hook-writable env file once and stores the resulting
|
||||
@@ -65,8 +64,8 @@ impl ShellEnvFile {
|
||||
/// The temp file remains an input channel for SessionStart hooks, but later
|
||||
/// commands receive only captured variable changes. Running both a baseline
|
||||
/// environment dump and a sourced dump lets shell syntax such as command
|
||||
/// substitution behave naturally without keeping `CODEX_ENV_FILE` available
|
||||
/// after hook execution.
|
||||
/// substitution behave naturally without keeping env-file path variables
|
||||
/// available after hook execution.
|
||||
pub(crate) async fn capture_exports(
|
||||
&self,
|
||||
shell: &Shell,
|
||||
@@ -79,6 +78,7 @@ impl ShellEnvFile {
|
||||
|
||||
let mut capture_env = base_env.clone();
|
||||
remove_env_var(&mut capture_env, CODEX_ENV_FILE_ENV_VAR);
|
||||
remove_env_var(&mut capture_env, CLAUDE_ENV_FILE_ENV_VAR);
|
||||
self.insert_path_into_env(&mut capture_env);
|
||||
|
||||
let baseline = self
|
||||
@@ -135,6 +135,7 @@ impl ShellEnvFile {
|
||||
insert_env_var(env, CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id);
|
||||
}
|
||||
remove_env_var(env, CODEX_ENV_FILE_ENV_VAR);
|
||||
remove_env_var(env, CLAUDE_ENV_FILE_ENV_VAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +318,7 @@ fn diff_env(
|
||||
fn ignored_capture_key(key: &str) -> bool {
|
||||
[
|
||||
CODEX_ENV_FILE_ENV_VAR,
|
||||
CLAUDE_ENV_FILE_ENV_VAR,
|
||||
CODEX_THREAD_ID_ENV_VAR,
|
||||
"PWD",
|
||||
"OLDPWD",
|
||||
|
||||
@@ -39,6 +39,7 @@ async fn shell_env_file_applies_exports_without_exposing_writable_path() -> Resu
|
||||
export CODEX_SESSION_START_TEST='from-session-start'
|
||||
export PATH=\"/plugin/bin:$PATH\"
|
||||
export CODEX_ENV_FILE='/tmp/poison'
|
||||
export CLAUDE_ENV_FILE='/tmp/poison'
|
||||
export CODEX_THREAD_ID='poisoned-thread'
|
||||
export EXPLICIT_OVERRIDE='from-hook'
|
||||
",
|
||||
@@ -53,6 +54,10 @@ export EXPLICIT_OVERRIDE='from-hook'
|
||||
CODEX_ENV_FILE_ENV_VAR.to_string(),
|
||||
env_file.path().display().to_string(),
|
||||
);
|
||||
env.insert(
|
||||
CLAUDE_ENV_FILE_ENV_VAR.to_string(),
|
||||
env_file.path().display().to_string(),
|
||||
);
|
||||
let explicit_env_overrides =
|
||||
HashMap::from([("EXPLICIT_OVERRIDE".to_string(), "from-policy".to_string())]);
|
||||
|
||||
@@ -181,6 +186,7 @@ Write-Output "hidden"
|
||||
$env:CODEX_SESSION_START_TEST = "from-session-start"
|
||||
$env:COMMAND_EXPRESSION = ("unsafe").ToUpperInvariant()
|
||||
$env:CODEX_ENV_FILE = "C:\poison"
|
||||
$env:CLAUDE_ENV_FILE = "C:\poison"
|
||||
$env:CODEX_THREAD_ID = "poisoned-thread"
|
||||
$env:EXPLICIT_OVERRIDE = "from-hook"
|
||||
Remove-Item Env:REMOVED_BY_HOOK -ErrorAction SilentlyContinue
|
||||
@@ -196,6 +202,10 @@ Remove-Item Env:REMOVED_BY_HOOK -ErrorAction SilentlyContinue
|
||||
CODEX_ENV_FILE_ENV_VAR.to_string(),
|
||||
env_file.path().display().to_string(),
|
||||
);
|
||||
env.insert(
|
||||
CLAUDE_ENV_FILE_ENV_VAR.to_string(),
|
||||
env_file.path().display().to_string(),
|
||||
);
|
||||
let explicit_env_overrides =
|
||||
HashMap::from([("EXPLICIT_OVERRIDE".to_string(), "from-policy".to_string())]);
|
||||
|
||||
|
||||
@@ -783,7 +783,7 @@ fn write_session_start_hook_exporting_env(home: &Path) -> Result<()> {
|
||||
let session_start_script = r#"import os
|
||||
from pathlib import Path
|
||||
|
||||
env_file = Path(os.environ["CODEX_ENV_FILE"])
|
||||
env_file = Path(os.environ["CLAUDE_ENV_FILE"])
|
||||
with env_file.open("a", encoding="utf-8") as handle:
|
||||
handle.write("export CODEX_SESSION_START_TEST='from-session-start'\n")
|
||||
"#;
|
||||
@@ -791,7 +791,7 @@ with env_file.open("a", encoding="utf-8") as handle:
|
||||
let pre_tool_use_script = r#"import os
|
||||
from pathlib import Path
|
||||
|
||||
env_file = os.environ.get("CODEX_ENV_FILE")
|
||||
env_file = os.environ.get("CODEX_ENV_FILE") or os.environ.get("CLAUDE_ENV_FILE")
|
||||
if env_file:
|
||||
with Path(env_file).open("a", encoding="utf-8") as handle:
|
||||
handle.write("export CODEX_SESSION_START_TEST='leaked-to-pre-tool-use'\n")
|
||||
@@ -2717,7 +2717,7 @@ async fn assert_session_start_env_file_reaches_bash_surface(
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let call_id = format!("session-start-env-file-{}", surface.slug());
|
||||
let command = "printf '%s|%s' \"$CODEX_SESSION_START_TEST\" \"${CODEX_ENV_FILE:+configured}\"";
|
||||
let command = "printf '%s|%s|%s' \"$CODEX_SESSION_START_TEST\" \"${CODEX_ENV_FILE:+configured}\" \"${CLAUDE_ENV_FILE:+configured}\"";
|
||||
let tool_call = match surface {
|
||||
BashRewriteSurface::ExecCommand => ev_function_call(
|
||||
&call_id,
|
||||
@@ -2773,12 +2773,12 @@ async fn assert_session_start_env_file_reaches_bash_surface(
|
||||
"expected hook-exported value in {surface:?} output: {output}"
|
||||
);
|
||||
assert!(
|
||||
output.contains("from-session-start|"),
|
||||
"expected CODEX_ENV_FILE to stay hidden from {surface:?} command: {output}"
|
||||
output.contains("from-session-start||"),
|
||||
"expected env-file paths to stay hidden from {surface:?} command: {output}"
|
||||
);
|
||||
assert!(
|
||||
!output.contains("leaked-to-pre-tool-use"),
|
||||
"PreToolUse should not receive CODEX_ENV_FILE: {output}"
|
||||
"PreToolUse should not receive env-file paths: {output}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -2795,7 +2795,7 @@ async fn session_start_env_file_exports_reach_exec_command() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shell_command_hides_codex_env_file_when_hooks_are_disabled() -> Result<()> {
|
||||
async fn shell_command_hides_env_file_paths_when_hooks_are_disabled() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
skip_if_windows!(Ok(()));
|
||||
|
||||
@@ -2810,7 +2810,7 @@ async fn shell_command_hides_codex_env_file_when_hooks_are_disabled() -> Result<
|
||||
call_id,
|
||||
"shell_command",
|
||||
&serde_json::to_string(&serde_json::json!({
|
||||
"command": "printf '%s' \"${CODEX_ENV_FILE:+configured}\"",
|
||||
"command": "printf '%s|%s' \"${CODEX_ENV_FILE:+configured}\" \"${CLAUDE_ENV_FILE:+configured}\"",
|
||||
"login": false,
|
||||
}))?,
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ use tokio::process::Command;
|
||||
use tokio::time::timeout;
|
||||
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::shell_environment::CLAUDE_ENV_FILE_ENV_VAR;
|
||||
use codex_protocol::shell_environment::CODEX_ENV_FILE_ENV_VAR;
|
||||
|
||||
use super::CommandShell;
|
||||
@@ -116,8 +117,9 @@ fn build_command(shell: &CommandShell, handler: &ConfiguredHandler) -> Command {
|
||||
command.arg(&handler.command);
|
||||
}
|
||||
command.envs(&handler.env);
|
||||
// Only SessionStart hooks receive CODEX_ENV_FILE.
|
||||
// Only SessionStart hooks receive session env-file paths.
|
||||
command.env_remove(CODEX_ENV_FILE_ENV_VAR);
|
||||
command.env_remove(CLAUDE_ENV_FILE_ENV_VAR);
|
||||
if handler.event_name == HookEventName::SessionStart {
|
||||
command.envs(&shell.env);
|
||||
}
|
||||
@@ -153,6 +155,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::process::Command;
|
||||
|
||||
use super::CLAUDE_ENV_FILE_ENV_VAR;
|
||||
use super::CODEX_ENV_FILE_ENV_VAR;
|
||||
use super::CommandShell;
|
||||
use super::ConfiguredHandler;
|
||||
@@ -162,10 +165,16 @@ mod tests {
|
||||
CommandShell {
|
||||
program: "hook-shell".to_string(),
|
||||
args: Vec::new(),
|
||||
env: HashMap::from([(
|
||||
CODEX_ENV_FILE_ENV_VAR.to_string(),
|
||||
"session-owned-env-file".to_string(),
|
||||
)]),
|
||||
env: HashMap::from([
|
||||
(
|
||||
CODEX_ENV_FILE_ENV_VAR.to_string(),
|
||||
"session-owned-env-file".to_string(),
|
||||
),
|
||||
(
|
||||
CLAUDE_ENV_FILE_ENV_VAR.to_string(),
|
||||
"session-owned-env-file".to_string(),
|
||||
),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,19 +201,24 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_session_start_hook_masks_inherited_codex_env_file() {
|
||||
fn non_session_start_hook_masks_inherited_env_file_paths() {
|
||||
let command = build_command(&shell(), &handler(HookEventName::PreToolUse));
|
||||
|
||||
assert_eq!(command_env(&command, CODEX_ENV_FILE_ENV_VAR), Some(None));
|
||||
assert_eq!(command_env(&command, CLAUDE_ENV_FILE_ENV_VAR), Some(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_start_hook_receives_session_owned_codex_env_file() {
|
||||
fn session_start_hook_receives_session_owned_env_file_paths() {
|
||||
let command = build_command(&shell(), &handler(HookEventName::SessionStart));
|
||||
|
||||
assert_eq!(
|
||||
command_env(&command, CODEX_ENV_FILE_ENV_VAR),
|
||||
Some(Some(OsStr::new("session-owned-env-file")))
|
||||
);
|
||||
assert_eq!(
|
||||
command_env(&command, CLAUDE_ENV_FILE_ENV_VAR),
|
||||
Some(Some(OsStr::new("session-owned-env-file")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_protocol::protocol::HookOutputEntry;
|
||||
use codex_protocol::protocol::HookOutputEntryKind;
|
||||
use codex_protocol::protocol::HookRunStatus;
|
||||
use codex_protocol::protocol::HookRunSummary;
|
||||
use codex_protocol::shell_environment::CLAUDE_ENV_FILE_ENV_VAR;
|
||||
use codex_protocol::shell_environment::CODEX_ENV_FILE_ENV_VAR;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
@@ -181,9 +182,10 @@ pub(crate) async fn run(
|
||||
};
|
||||
|
||||
let results = if event_name == HookEventName::SessionStart
|
||||
&& shell.env.contains_key(CODEX_ENV_FILE_ENV_VAR)
|
||||
&& (shell.env.contains_key(CODEX_ENV_FILE_ENV_VAR)
|
||||
|| shell.env.contains_key(CLAUDE_ENV_FILE_ENV_VAR))
|
||||
{
|
||||
// Serialize hooks that share CODEX_ENV_FILE to preserve configured order.
|
||||
// Serialize hooks that share the session env file to preserve configured order.
|
||||
let mut results = Vec::with_capacity(matched.len());
|
||||
for (completion_order, handler) in matched.into_iter().enumerate() {
|
||||
let result = run_command(shell, &handler, &input_json, request.cwd.as_path()).await;
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::config_types::ShellEnvironmentPolicyInherit;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const CODEX_ENV_FILE_ENV_VAR: &str = "CODEX_ENV_FILE";
|
||||
pub const CLAUDE_ENV_FILE_ENV_VAR: &str = "CLAUDE_ENV_FILE";
|
||||
pub const CODEX_THREAD_ID_ENV_VAR: &str = "CODEX_THREAD_ID";
|
||||
|
||||
/// Construct a shell environment from the supplied process environment and
|
||||
|
||||
Reference in New Issue
Block a user