Add CLAUDE_ENV_FILE compatibility alias

This commit is contained in:
Abhinav Vedmala
2026-05-28 22:36:01 -07:00
parent 01026b5f24
commit 09c8e31c90
6 changed files with 53 additions and 24 deletions
+9 -7
View File
@@ -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",
+10
View File
@@ -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())]);
+8 -8
View File
@@ -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,
}))?,
),
+21 -7
View File
@@ -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")))
);
}
}
+4 -2
View 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