Use AbsolutePathBuf for cwd state (#15710)

Migrate `cwd` and related session/config state to `AbsolutePathBuf` so
downstream consumers consistently see absolute working directories.

Add test-only `.abs()` helpers for `Path`, `PathBuf`, and `TempDir`, and
update branch-local tests to use them instead of
`AbsolutePathBuf::try_from(...)`.

For the remaining TUI/app-server snapshot coverage that renders absolute
cwd values, keep the snapshots unchanged and skip the Windows-only cases
where the platform-specific absolute path layout differs.
This commit is contained in:
pakrym-oai
2026-03-25 09:02:22 -07:00
committed by GitHub
Unverified
parent 178c3b15b4
commit 504aeb0e09
65 changed files with 717 additions and 422 deletions
+1 -1
View File
@@ -252,7 +252,7 @@ mod reload {
fn reload_overrides(config: &Config, preserve_current_provider: bool) -> ConfigOverrides {
ConfigOverrides {
cwd: Some(config.cwd.clone()),
cwd: Some(config.cwd.to_path_buf()),
model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
+40 -37
View File
@@ -33,6 +33,7 @@ use crate::models_manager::manager::ModelsManager;
use crate::models_manager::manager::RefreshStrategy;
use crate::parse_command::parse_command;
use crate::parse_turn_item;
use crate::path_utils::normalize_for_native_workdir;
use crate::realtime_conversation::RealtimeConversationManager;
use crate::realtime_conversation::handle_audio as handle_realtime_conversation_audio;
use crate::realtime_conversation::handle_close as handle_realtime_conversation_close;
@@ -835,10 +836,10 @@ pub(crate) struct TurnContext {
pub(crate) reasoning_summary: ReasoningSummaryConfig,
pub(crate) session_source: SessionSource,
pub(crate) environment: Arc<Environment>,
/// The session's current working directory. All relative paths provided by
/// the model as well as sandbox policies are resolved against this path
/// The session's absolute working directory. All relative paths provided
/// by the model as well as sandbox policies are resolved against this path
/// instead of `std::env::current_dir()`.
pub(crate) cwd: PathBuf,
pub(crate) cwd: AbsolutePathBuf,
pub(crate) current_date: Option<String>,
pub(crate) timezone: Option<String>,
pub(crate) app_server_client_name: Option<String>,
@@ -979,7 +980,7 @@ impl TurnContext {
pub(crate) fn resolve_path(&self, path: Option<String>) -> PathBuf {
path.as_ref()
.map(PathBuf::from)
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
.map_or_else(|| self.cwd.to_path_buf(), |p| self.cwd.as_path().join(p))
}
pub(crate) fn compact_prompt(&self) -> &str {
@@ -992,7 +993,7 @@ impl TurnContext {
TurnContextItem {
turn_id: Some(self.sub_id.clone()),
trace_id: self.trace_id.clone(),
cwd: self.cwd.clone(),
cwd: self.cwd.to_path_buf(),
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
@@ -1068,14 +1069,11 @@ pub(crate) struct SessionConfiguration {
network_sandbox_policy: NetworkSandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel,
/// Working directory that should be treated as the *root* of the
/// Absolute working directory that should be treated as the *root* of the
/// session. All relative paths supplied by the model as well as the
/// execution sandbox are resolved against this directory **instead**
/// of the process-wide current working directory. CLI front-ends are
/// expected to expand this to an absolute path before sending the
/// `ConfigureSession` operation so that the business-logic layer can
/// operate deterministically.
cwd: PathBuf,
/// execution sandbox are resolved against this directory **instead** of
/// the process-wide current working directory.
cwd: AbsolutePathBuf,
/// Directory containing all Codex state for this session.
codex_home: PathBuf,
/// Optional user-facing name for the thread, updated during the session.
@@ -1107,7 +1105,7 @@ impl SessionConfiguration {
approval_policy: self.approval_policy.value(),
approvals_reviewer: self.approvals_reviewer,
sandbox_policy: self.sandbox_policy.get().clone(),
cwd: self.cwd.clone(),
cwd: self.cwd.to_path_buf(),
ephemeral: self.original_config_do_not_use.ephemeral,
reasoning_effort: self.collaboration_mode.reasoning_effort(),
personality: self.personality,
@@ -1150,11 +1148,23 @@ impl SessionConfiguration {
if let Some(windows_sandbox_level) = updates.windows_sandbox_level {
next_configuration.windows_sandbox_level = windows_sandbox_level;
}
let mut cwd_changed = false;
if let Some(cwd) = updates.cwd.clone() {
next_configuration.cwd = cwd;
cwd_changed = true;
}
let absolute_cwd = updates
.cwd
.as_ref()
.map(|cwd| {
AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(
cwd.as_path(),
))
.unwrap_or_else(|e| {
warn!("failed to normalize update cwd: {cwd:?}: {e}");
self.cwd.clone()
})
})
.unwrap_or_else(|| self.cwd.clone());
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
next_configuration.cwd = absolute_cwd;
if sandbox_policy_changed || (cwd_changed && file_system_policy_matches_legacy) {
// Preserve richer split policies across cwd-only updates; only
// rederive when the session is already using the legacy bridge.
@@ -1351,8 +1361,6 @@ impl Session {
let auth_manager_for_context = auth_manager;
let provider_for_context = provider;
let session_telemetry_for_context = session_telemetry;
let per_turn_config = Arc::new(per_turn_config);
let tools_config = ToolsConfig::new(&ToolsConfigParams {
model_info: &model_info,
available_models: &models_manager.try_list_models().unwrap_or_default(),
@@ -1372,10 +1380,12 @@ impl Session {
.with_agent_roles(per_turn_config.agent_roles.clone());
let cwd = session_configuration.cwd.clone();
let per_turn_config = Arc::new(per_turn_config);
let turn_metadata_state = Arc::new(TurnMetadataState::new(
conversation_id.to_string(),
sub_id.clone(),
cwd.clone(),
cwd.to_path_buf(),
session_configuration.sandbox_policy.get(),
session_configuration.windows_sandbox_level,
));
@@ -1447,13 +1457,6 @@ impl Session {
session_configuration.collaboration_mode.model(),
session_configuration.provider
);
if !session_configuration.cwd.is_absolute() {
return Err(anyhow::anyhow!(
"cwd is not absolute: {:?}",
session_configuration.cwd
));
}
let forked_from_id = initial_history.forked_from_id();
let (conversation_id, rollout_params) = match &initial_history {
@@ -1723,7 +1726,7 @@ impl Session {
ShellSnapshot::start_snapshotting(
config.codex_home.clone(),
conversation_id,
session_configuration.cwd.clone(),
session_configuration.cwd.to_path_buf(),
&mut default_shell,
session_telemetry.clone(),
)
@@ -1922,7 +1925,7 @@ impl Session {
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
cwd: session_configuration.cwd.clone(),
cwd: session_configuration.cwd.to_path_buf(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
history_log_id,
history_entry_count,
@@ -1943,7 +1946,7 @@ impl Session {
let sandbox_state = SandboxState {
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: session_configuration.cwd.clone(),
sandbox_cwd: session_configuration.cwd.to_path_buf(),
use_legacy_landlock: config.features.use_legacy_landlock(),
};
let mut required_mcp_servers: Vec<String> = mcp_servers
@@ -2407,7 +2410,7 @@ impl Session {
let sandbox_state = SandboxState {
sandbox_policy: per_turn_config.permissions.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: per_turn_config.cwd.clone(),
sandbox_cwd: per_turn_config.cwd.to_path_buf(),
use_legacy_landlock: per_turn_config.features.use_legacy_landlock(),
};
if let Err(e) = self
@@ -4138,7 +4141,7 @@ impl Session {
let sandbox_state = SandboxState {
sandbox_policy: turn_context.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
sandbox_cwd: turn_context.cwd.clone(),
sandbox_cwd: turn_context.cwd.to_path_buf(),
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
};
{
@@ -4919,7 +4922,7 @@ mod handlers {
) {
let cwds = if cwds.is_empty() {
let state = sess.state.lock().await;
vec![state.session_configuration.cwd.clone()]
vec![state.session_configuration.cwd.to_path_buf()]
} else {
cwds
};
@@ -5363,7 +5366,7 @@ async fn spawn_review_thread(
let turn_metadata_state = Arc::new(TurnMetadataState::new(
sess.conversation_id.to_string(),
review_turn_id.clone(),
parent_turn_context.cwd.clone(),
parent_turn_context.cwd.to_path_buf(),
parent_turn_context.sandbox_policy.get(),
parent_turn_context.windows_sandbox_level,
));
@@ -5852,7 +5855,7 @@ pub(crate) async fn run_turn(
let stop_request = codex_hooks::StopRequest {
session_id: sess.conversation_id,
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: stop_hook_permission_mode,
@@ -5902,7 +5905,7 @@ pub(crate) async fn run_turn(
.hooks()
.dispatch(HookPayload {
session_id: sess.conversation_id,
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
client: turn_context.app_server_client_name.clone(),
triggered_at: chrono::Utc::now(),
hook_event: HookEvent::AfterAgent {
@@ -62,7 +62,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -101,7 +101,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
let mut previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -851,7 +851,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -923,7 +923,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
serde_json::to_value(Some(TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -952,7 +952,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -1058,7 +1058,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
let current_context_item = TurnContextItem {
turn_id: Some(current_turn_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -1160,7 +1160,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -1304,7 +1304,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
+2 -3
View File
@@ -22,7 +22,6 @@ use codex_protocol::request_permissions::RequestPermissionsResponse;
use codex_protocol::request_user_input::RequestUserInputArgs;
use codex_protocol::request_user_input::RequestUserInputResponse;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde_json::Value;
use std::time::Duration;
use tokio::sync::Mutex;
@@ -518,7 +517,7 @@ async fn handle_patch_approval(
let change_count = changes.len();
let maybe_files = changes
.keys()
.map(|path| AbsolutePathBuf::from_absolute_path(parent_ctx.cwd.join(path)).ok())
.map(|path| parent_ctx.cwd.join(path).ok())
.collect::<Option<Vec<_>>>();
if let Some(files) = maybe_files {
let review_cancel = cancel_token.child_token();
@@ -554,7 +553,7 @@ async fn handle_patch_approval(
Arc::clone(parent_ctx),
GuardianApprovalRequest::ApplyPatch {
id: approval_id.clone(),
cwd: parent_ctx.cwd.clone(),
cwd: parent_ctx.cwd.to_path_buf(),
files,
change_count,
patch,
+38 -9
View File
@@ -80,6 +80,7 @@ use codex_protocol::protocol::ConversationAudioParams;
use codex_protocol::protocol::RealtimeAudioFrame;
use codex_protocol::protocol::Submission;
use codex_protocol::protocol::W3cTraceContext;
use core_test_support::PathBufExt;
use core_test_support::context_snapshot;
use core_test_support::context_snapshot::ContextSnapshotOptions;
use core_test_support::context_snapshot::ContextSnapshotRenderMode;
@@ -1266,7 +1267,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
let previous_context_item = TurnContextItem {
turn_id: Some(turn_context.sub_id.clone()),
trace_id: turn_context.trace_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
@@ -2282,10 +2283,9 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
let original_cwd = project_root.join("subdir");
let docs_dir = original_cwd.join("docs");
std::fs::create_dir_all(&docs_dir).expect("create docs dir");
let docs_dir =
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs_dir).expect("docs");
let docs_dir = docs_dir.abs();
session_configuration.cwd = original_cwd;
session_configuration.cwd = original_cwd.abs();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
@@ -2407,10 +2407,9 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
let original_cwd = project_root.join("subdir");
let docs_dir = original_cwd.join("docs");
std::fs::create_dir_all(&docs_dir).expect("create docs dir");
let docs_dir =
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs_dir).expect("docs");
let docs_dir = docs_dir.abs();
session_configuration.cwd = original_cwd;
session_configuration.cwd = original_cwd.abs();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
@@ -2444,6 +2443,36 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
);
}
#[tokio::test]
async fn session_update_settings_keeps_runtime_cwds_absolute() {
let (session, turn_context) = make_session_and_context().await;
let updated_cwd = turn_context
.cwd
.join("project")
.expect("resolve project dir");
std::fs::create_dir_all(updated_cwd.as_path()).expect("create project dir");
session
.update_settings(SessionSettingsUpdate {
cwd: Some(PathBuf::from("project")),
..Default::default()
})
.await
.expect("cwd update should succeed");
let session_cwd = {
let state = session.state.lock().await;
state.session_configuration.cwd.clone()
};
let config = session.get_config().await;
let next_turn = session.new_default_turn().await;
assert_eq!(session_cwd, updated_cwd);
assert_eq!(config.cwd, turn_context.cwd);
assert_eq!(next_turn.cwd, updated_cwd);
assert_eq!(next_turn.config.cwd, updated_cwd);
}
#[tokio::test]
async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
let codex_home = tempfile::tempdir().expect("create temp dir");
@@ -3058,7 +3087,7 @@ async fn user_turn_updates_approvals_reviewer() {
text: "hello".to_string(),
text_elements: Vec::new(),
}],
cwd: config.cwd.clone(),
cwd: config.cwd.to_path_buf(),
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: Some(crate::config::types::ApprovalsReviewer::GuardianSubagent),
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
@@ -5060,7 +5089,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
"echo hi".to_string(),
]
},
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
expiration: timeout_ms.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env: HashMap::new(),
+5 -5
View File
@@ -23,7 +23,8 @@ use codex_protocol::models::ResponseItem;
use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathExt;
use core_test_support::TempDirExt;
use core_test_support::codex_linux_sandbox_exe_or_skip;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
@@ -123,7 +124,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
"echo hi".to_string(),
]
},
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
expiration: expiration_ms.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env: HashMap::new(),
@@ -388,12 +389,11 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
.expect("write policy file");
let mut config = build_test_config(codex_home.path()).await;
config.cwd = project_dir.path().to_path_buf();
config.cwd = project_dir.abs();
config.config_layer_stack = ConfigLayerStack::new(
vec![ConfigLayerEntry::new(
ConfigLayerSource::Project {
dot_codex_folder: AbsolutePathBuf::from_absolute_path(project_dir.path())
.expect("absolute project path"),
dot_codex_folder: project_dir.path().abs(),
},
toml::Value::Table(Default::default()),
)],
+43 -21
View File
@@ -27,6 +27,9 @@ use serde::Deserialize;
use tempfile::tempdir;
use super::*;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use core_test_support::TempDirExt;
use core_test_support::test_absolute_path;
use pretty_assertions::assert_eq;
@@ -77,6 +80,23 @@ fn http_mcp(url: &str) -> McpServerConfig {
}
}
#[test]
fn load_config_normalizes_relative_cwd_override() -> std::io::Result<()> {
let expected_cwd = AbsolutePathBuf::relative_to_current_dir("nested")?;
let codex_home = tempdir()?;
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides {
cwd: Some(PathBuf::from("nested")),
..Default::default()
},
codex_home.abs().into_path_buf(),
)?;
assert_eq!(config.cwd, expected_cwd);
Ok(())
}
#[test]
fn test_toml_parsing() {
let history_with_persistence = r#"
@@ -460,7 +480,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
codex_home.path().to_path_buf(),
)?;
let memories_root = AbsolutePathBuf::try_from(codex_home.path().join("memories")).unwrap();
let memories_root = codex_home.path().join("memories").abs();
assert_eq!(
config.permissions.file_system_sandbox_policy,
FileSystemSandboxPolicy::restricted(vec![
@@ -496,9 +516,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
writable_roots: vec![memories_root],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![
AbsolutePathBuf::try_from(cwd.path().join("docs")).expect("absolute docs path"),
],
readable_roots: vec![cwd.path().join("docs").abs(),],
},
network_access: false,
exclude_tmpdir_env_var: true,
@@ -1277,7 +1295,7 @@ fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<()> {
temp_dir.path().to_path_buf(),
)?;
let expected_backend = AbsolutePathBuf::try_from(backend).unwrap();
let expected_backend = backend.abs();
if cfg!(target_os = "windows") {
match config.permissions.sandbox_policy.get() {
SandboxPolicy::ReadOnly { .. } => {}
@@ -1327,7 +1345,7 @@ fn workspace_write_always_includes_memories_root_once() -> std::io::Result<()> {
let config = Config::load_from_base_config_with_overrides(
ConfigToml {
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(&memories_root)?],
writable_roots: vec![memories_root.abs()],
..Default::default()
}),
..Default::default()
@@ -1350,7 +1368,7 @@ fn workspace_write_always_includes_memories_root_once() -> std::io::Result<()> {
"expected memories root directory to exist at {}",
memories_root.display()
);
let expected_memories_root = AbsolutePathBuf::from_absolute_path(&memories_root)?;
let expected_memories_root = memories_root.abs();
match config.permissions.sandbox_policy.get() {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert_eq!(
@@ -1768,7 +1786,7 @@ async fn managed_config_overrides_oauth_store_mode() -> anyhow::Result<()> {
macos_managed_config_requirements_base64: None,
};
let cwd = AbsolutePathBuf::try_from(codex_home.path())?;
let cwd = codex_home.path().abs();
let config_layer_stack = load_config_layers_state(
codex_home.path(),
Some(cwd),
@@ -1897,7 +1915,7 @@ async fn managed_config_wins_over_cli_overrides() -> anyhow::Result<()> {
macos_managed_config_requirements_base64: None,
};
let cwd = AbsolutePathBuf::try_from(codex_home.path())?;
let cwd = codex_home.path().abs();
let config_layer_stack = load_config_layers_state(
codex_home.path(),
Some(cwd),
@@ -2933,7 +2951,11 @@ struct PrecedenceTestFixture {
}
impl PrecedenceTestFixture {
fn cwd(&self) -> PathBuf {
fn cwd(&self) -> AbsolutePathBuf {
self.cwd.abs()
}
fn cwd_path(&self) -> PathBuf {
self.cwd.path().to_path_buf()
}
@@ -2974,7 +2996,7 @@ fn loads_compact_prompt_from_file() -> std::io::Result<()> {
std::fs::write(&prompt_path, " summarize differently ")?;
let cfg = ConfigToml {
experimental_compact_prompt_file: Some(AbsolutePathBuf::from_absolute_path(prompt_path)?),
experimental_compact_prompt_file: Some(prompt_path.abs()),
..Default::default()
};
@@ -3071,7 +3093,7 @@ fn load_config_rejects_missing_agent_role_config_file() -> std::io::Result<()> {
"researcher".to_string(),
AgentRoleToml {
description: Some("Research role".to_string()),
config_file: Some(AbsolutePathBuf::from_absolute_path(missing_path)?),
config_file: Some(missing_path.abs()),
nickname_candidates: None,
},
)]),
@@ -4082,7 +4104,7 @@ fn model_catalog_json_loads_from_path() -> std::io::Result<()> {
)?;
let cfg = ConfigToml {
model_catalog_json: Some(AbsolutePathBuf::from_absolute_path(catalog_path)?),
model_catalog_json: Some(catalog_path.abs()),
..Default::default()
};
@@ -4103,7 +4125,7 @@ fn model_catalog_json_rejects_empty_catalog() -> std::io::Result<()> {
std::fs::write(&catalog_path, r#"{"models":[]}"#)?;
let cfg = ConfigToml {
model_catalog_json: Some(AbsolutePathBuf::from_absolute_path(catalog_path)?),
model_catalog_json: Some(catalog_path.abs()),
..Default::default()
};
@@ -4240,7 +4262,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
let o3_profile_overrides = ConfigOverrides {
config_profile: Some("o3".to_string()),
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
};
let o3_profile_config: Config = Config::load_from_base_config_with_overrides(
@@ -4368,7 +4390,7 @@ fn metrics_exporter_defaults_to_statsig_when_missing() -> std::io::Result<()> {
let config = Config::load_from_base_config_with_overrides(
fixture.cfg.clone(),
ConfigOverrides {
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
},
fixture.codex_home(),
@@ -4384,7 +4406,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
let gpt3_profile_overrides = ConfigOverrides {
config_profile: Some("gpt3".to_string()),
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
};
let gpt3_profile_config = Config::load_from_base_config_with_overrides(
@@ -4505,7 +4527,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
// Verify that loading without specifying a profile in ConfigOverrides
// uses the default profile from the config file (which is "gpt3").
let default_profile_overrides = ConfigOverrides {
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
};
@@ -4525,7 +4547,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
let zdr_profile_overrides = ConfigOverrides {
config_profile: Some("zdr".to_string()),
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
};
let zdr_profile_config = Config::load_from_base_config_with_overrides(
@@ -4652,7 +4674,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
let gpt5_profile_overrides = ConfigOverrides {
config_profile: Some("gpt5".to_string()),
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
};
let gpt5_profile_config = Config::load_from_base_config_with_overrides(
@@ -4820,7 +4842,7 @@ fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> any
let config = Config::load_config_with_layer_stack(
fixture.cfg.clone(),
ConfigOverrides {
cwd: Some(fixture.cwd()),
cwd: Some(fixture.cwd_path()),
..Default::default()
},
fixture.codex_home(),
+22 -17
View File
@@ -395,10 +395,10 @@ pub struct Config {
/// Syntax highlighting theme override (kebab-case name).
pub tui_theme: Option<String>,
/// The directory that should be treated as the current working directory
/// for the session. All relative paths inside the business-logic layer are
/// resolved against this path.
pub cwd: PathBuf,
/// The absolute directory that should be treated as the current working
/// directory for the session. All relative paths inside the business-logic
/// layer are resolved against this path.
pub cwd: AbsolutePathBuf,
/// Preferred store for CLI auth credentials.
/// file (default): Use a file in the Codex home directory.
@@ -683,7 +683,7 @@ impl ConfigBuilder {
let loader_overrides = loader_overrides.unwrap_or_default();
let cwd_override = harness_overrides.cwd.as_deref().or(fallback_cwd.as_deref());
let cwd = match cwd_override {
Some(path) => AbsolutePathBuf::try_from(path)?,
Some(path) => AbsolutePathBuf::relative_to_current_dir(path)?,
None => AbsolutePathBuf::current_dir()?,
};
harness_overrides.cwd = Some(cwd.to_path_buf());
@@ -2104,7 +2104,7 @@ impl Config {
let windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg, &config_profile);
let windows_sandbox_private_desktop =
resolve_windows_sandbox_private_desktop(&cfg, &config_profile);
let resolved_cwd = normalize_for_native_workdir({
let resolved_cwd = AbsolutePathBuf::try_from(normalize_for_native_workdir({
use std::env;
match cwd {
@@ -2121,13 +2121,13 @@ impl Config {
current
}
}
});
}))?;
let mut additional_writable_roots: Vec<AbsolutePathBuf> = additional_writable_roots
.into_iter()
.map(|path| AbsolutePathBuf::resolve_path_against_base(path, &resolved_cwd))
.map(|path| AbsolutePathBuf::resolve_path_against_base(path, resolved_cwd.as_path()))
.collect::<Result<Vec<_>, _>>()?;
let active_project = cfg
.get_active_project(&resolved_cwd)
.get_active_project(resolved_cwd.as_path())
.unwrap_or(ProjectConfig { trust_level: None });
let permission_config_syntax = resolve_permission_config_syntax(
&config_layer_stack,
@@ -2200,12 +2200,15 @@ impl Config {
&mut startup_warnings,
)?;
let mut sandbox_policy = file_system_sandbox_policy
.to_legacy_sandbox_policy(network_sandbox_policy, &resolved_cwd)?;
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
file_system_sandbox_policy = file_system_sandbox_policy
.with_additional_writable_roots(&resolved_cwd, &additional_writable_roots);
.with_additional_writable_roots(
resolved_cwd.as_path(),
&additional_writable_roots,
);
sandbox_policy = file_system_sandbox_policy
.to_legacy_sandbox_policy(network_sandbox_policy, &resolved_cwd)?;
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
}
(
configured_network_proxy_config,
@@ -2219,7 +2222,7 @@ impl Config {
sandbox_mode,
config_profile.sandbox_mode,
windows_sandbox_level,
&resolved_cwd,
resolved_cwd.as_path(),
Some(&constrained_sandbox_policy),
);
if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy {
@@ -2229,8 +2232,10 @@ impl Config {
}
}
}
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, &resolved_cwd);
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&sandbox_policy,
resolved_cwd.as_path(),
);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
(
configured_network_proxy_config,
@@ -2566,11 +2571,11 @@ impl Config {
} else {
FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&effective_sandbox_policy,
&resolved_cwd,
resolved_cwd.as_path(),
)
};
let effective_file_system_sandbox_policy = effective_file_system_sandbox_policy
.with_additional_readable_roots(&resolved_cwd, &helper_readable_roots);
.with_additional_readable_roots(resolved_cwd.as_path(), &helper_readable_roots);
let effective_network_sandbox_policy =
if effective_sandbox_policy == original_sandbox_policy {
network_sandbox_policy
+3 -3
View File
@@ -70,8 +70,8 @@ impl EnvironmentContext {
) -> Self {
let before_network = Self::network_from_turn_context_item(before);
let after_network = Self::network_from_turn_context(after);
let cwd = if before.cwd != after.cwd {
Some(after.cwd.clone())
let cwd = if before.cwd.as_path() != after.cwd.as_path() {
Some(after.cwd.to_path_buf())
} else {
None
};
@@ -94,7 +94,7 @@ impl EnvironmentContext {
pub fn from_turn_context(turn_context: &TurnContext, shell: &Shell) -> Self {
Self::new(
Some(turn_context.cwd.clone()),
Some(turn_context.cwd.to_path_buf()),
shell.clone(),
turn_context.current_date.clone(),
turn_context.timezone.clone(),
+2 -2
View File
@@ -140,7 +140,7 @@ impl GuardianReviewSessionReuseKey {
base_instructions: spawn_config.base_instructions.clone(),
user_instructions: spawn_config.user_instructions.clone(),
compact_prompt: spawn_config.compact_prompt.clone(),
cwd: spawn_config.cwd.clone(),
cwd: spawn_config.cwd.to_path_buf(),
mcp_servers: spawn_config.mcp_servers.clone(),
codex_linux_sandbox_exe: spawn_config.codex_linux_sandbox_exe.clone(),
main_execve_wrapper_exe: spawn_config.main_execve_wrapper_exe.clone(),
@@ -512,7 +512,7 @@ async fn run_review_on_session(
.codex
.submit(Op::UserTurn {
items: params.prompt_items.clone(),
cwd: params.parent_turn.cwd.clone(),
cwd: params.parent_turn.cwd.to_path_buf(),
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
+6 -5
View File
@@ -25,7 +25,8 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::GuardianAssessmentStatus;
use codex_protocol::protocol::GuardianRiskLevel;
use codex_protocol::protocol::ReviewDecision;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathBufExt;
use core_test_support::TempDirExt;
use core_test_support::context_snapshot;
use core_test_support::context_snapshot::ContextSnapshotOptions;
use core_test_support::responses::ev_assistant_message;
@@ -322,7 +323,7 @@ fn guardian_assessment_action_value_redacts_apply_patch_patch_text() {
("/tmp", "/tmp/guardian.txt")
};
let cwd = PathBuf::from(cwd);
let file = AbsolutePathBuf::try_from(file).expect("absolute path");
let file = PathBuf::from(file).abs();
let action = GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: cwd.clone(),
@@ -356,7 +357,7 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() {
let apply_patch = GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: PathBuf::from("/tmp"),
files: vec![AbsolutePathBuf::try_from("/tmp/guardian.txt").expect("absolute path")],
files: vec![PathBuf::from("/tmp/guardian.txt").abs()],
change_count: 1usize,
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
.to_string(),
@@ -384,7 +385,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: PathBuf::from("/tmp"),
files: vec![AbsolutePathBuf::try_from("/tmp/guardian.txt").expect("absolute path")],
files: vec![PathBuf::from("/tmp/guardian.txt").abs()],
change_count: 1usize,
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
.to_string(),
@@ -512,7 +513,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
let (mut session, mut turn) = crate::codex::make_session_and_context().await;
let temp_cwd = TempDir::new()?;
let mut config = (*turn.config).clone();
config.cwd = temp_cwd.path().to_path_buf();
config.cwd = temp_cwd.abs();
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
let config = Arc::new(config);
let models_manager = Arc::new(test_support::models_manager_with_provider(
+3 -3
View File
@@ -92,7 +92,7 @@ pub(crate) async fn run_pending_session_start_hooks(
let request = codex_hooks::SessionStartRequest {
session_id: sess.conversation_id,
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
@@ -120,7 +120,7 @@ pub(crate) async fn run_pre_tool_use_hooks(
let request = PreToolUseRequest {
session_id: sess.conversation_id,
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
@@ -149,7 +149,7 @@ pub(crate) async fn run_user_prompt_submit_hooks(
let request = UserPromptSubmitRequest {
session_id: sess.conversation_id,
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
cwd: turn_context.cwd.to_path_buf(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
+10 -1
View File
@@ -266,7 +266,16 @@ mod agent {
let root = memory_root(&config.codex_home);
let mut agent_config = config.as_ref().clone();
agent_config.cwd = root;
match AbsolutePathBuf::from_absolute_path(root) {
Ok(root) => agent_config.cwd = root,
Err(err) => {
warn!(
"memory phase-2 consolidation could not set cwd from codex_home {}: {err}",
agent_config.codex_home.display()
);
return None;
}
}
// Consolidation threads must never feed back into phase-1 memory generation.
agent_config.memories.generate_memories = false;
// Approval policy
+5 -4
View File
@@ -435,6 +435,7 @@ mod phase2 {
use codex_state::Phase2JobClaimOutcome;
use codex_state::Stage1Output;
use codex_state::ThreadMetadataBuilder;
use core_test_support::PathBufExt;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -469,7 +470,7 @@ mod phase2 {
let codex_home = tempfile::tempdir().expect("create temp codex home");
let mut config = test_config();
config.codex_home = codex_home.path().to_path_buf();
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
let config = Arc::new(config);
let state_db = codex_state::StateRuntime::init(
@@ -507,7 +508,7 @@ mod phase2 {
Utc::now(),
SessionSource::Cli,
);
metadata_builder.cwd = self.config.cwd.clone();
metadata_builder.cwd = self.config.cwd.to_path_buf();
metadata_builder.model_provider = Some(self.config.model_provider_id.clone());
let metadata = metadata_builder.build(&self.config.model_provider_id);
@@ -882,7 +883,7 @@ mod phase2 {
let codex_home = tempfile::tempdir().expect("create temp codex home");
let mut config = test_config();
config.codex_home = codex_home.path().to_path_buf();
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
let config = Arc::new(config);
let state_db = codex_state::StateRuntime::init(
@@ -904,7 +905,7 @@ mod phase2 {
Utc::now(),
SessionSource::Cli,
);
metadata_builder.cwd = config.cwd.clone();
metadata_builder.cwd = config.cwd.to_path_buf();
metadata_builder.model_provider = Some(config.model_provider_id.clone());
let metadata = metadata_builder.build(&config.model_provider_id);
state_db
+1 -1
View File
@@ -184,7 +184,7 @@ pub async fn read_project_docs(config: &Config) -> std::io::Result<Option<String
/// directory (inclusive). Symlinks are allowed. When `project_doc_max_bytes`
/// is zero, returns an empty list.
pub fn discover_project_doc_paths(config: &Config) -> std::io::Result<Vec<PathBuf>> {
let mut dir = config.cwd.clone();
let mut dir = config.cwd.to_path_buf();
if let Ok(canon) = normalize_path(&dir) {
dir = canon;
}
+8 -6
View File
@@ -1,6 +1,8 @@
use super::*;
use crate::config::ConfigBuilder;
use codex_features::Feature;
use core_test_support::PathBufExt;
use core_test_support::TempDirExt;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
@@ -18,7 +20,7 @@ async fn make_config(root: &TempDir, limit: usize, instructions: Option<&str>) -
.await
.expect("defaults for test should always succeed");
config.cwd = root.path().to_path_buf();
config.cwd = root.abs();
config.project_doc_max_bytes = limit;
config.user_instructions = instructions.map(ToOwned::to_owned);
@@ -62,7 +64,7 @@ async fn make_config_with_project_root_markers(
.await
.expect("defaults for test should always succeed");
config.cwd = root.path().to_path_buf();
config.cwd = root.abs();
config.project_doc_max_bytes = limit;
config.user_instructions = instructions.map(ToOwned::to_owned);
config
@@ -136,7 +138,7 @@ async fn finds_doc_in_repo_root() {
// Build config pointing at the nested dir.
let mut cfg = make_config(&repo, 4096, None).await;
cfg.cwd = nested;
cfg.cwd = nested.abs();
let res = get_user_instructions(&cfg).await.expect("doc expected");
assert_eq!(res, "root level doc");
@@ -261,7 +263,7 @@ async fn concatenates_root_and_cwd_docs() {
fs::write(nested.join("AGENTS.md"), "crate doc").unwrap();
let mut cfg = make_config(&repo, 4096, None).await;
cfg.cwd = nested;
cfg.cwd = nested.abs();
let res = get_user_instructions(&cfg).await.expect("doc expected");
assert_eq!(res, "root doc\n\ncrate doc");
@@ -278,13 +280,13 @@ async fn project_root_markers_are_honored_for_agents_discovery() {
fs::write(nested.join("AGENTS.md"), "child doc").unwrap();
let mut cfg = make_config_with_project_root_markers(&root, 4096, None, &[".codex-root"]).await;
cfg.cwd = nested;
cfg.cwd = nested.abs();
let discovery = discover_project_doc_paths(&cfg).expect("discover paths");
let expected_parent =
dunce::canonicalize(root.path().join("AGENTS.md")).expect("canonical parent doc path");
let expected_child =
dunce::canonicalize(cfg.cwd.join("AGENTS.md")).expect("canonical child doc path");
dunce::canonicalize(cfg.cwd.as_path().join("AGENTS.md")).expect("canonical child doc path");
assert_eq!(discovery.len(), 2);
assert_eq!(discovery[0], expected_parent);
assert_eq!(discovery[1], expected_child);
+5 -5
View File
@@ -145,7 +145,7 @@ pub(crate) async fn execute_user_shell_command(
process_id: None,
turn_id: turn_context.sub_id.clone(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.to_path_buf(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -156,7 +156,7 @@ pub(crate) async fn execute_user_shell_command(
let sandbox_policy = SandboxPolicy::DangerFullAccess;
let exec_env = ExecRequest {
command: exec_command.clone(),
cwd: cwd.clone(),
cwd: cwd.to_path_buf(),
env: create_env(
&turn_context.shell_environment_policy,
Some(session.conversation_id),
@@ -221,7 +221,7 @@ pub(crate) async fn execute_user_shell_command(
process_id: None,
turn_id: turn_context.sub_id.clone(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.to_path_buf(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -245,7 +245,7 @@ pub(crate) async fn execute_user_shell_command(
process_id: None,
turn_id: turn_context.sub_id.clone(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.to_path_buf(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -289,7 +289,7 @@ pub(crate) async fn execute_user_shell_command(
process_id: None,
turn_id: turn_context.sub_id.clone(),
command: display_command,
cwd,
cwd: cwd.to_path_buf(),
parsed_cmd,
source: ExecCommandSource::UserShell,
interaction_input: None,
+6 -5
View File
@@ -12,6 +12,7 @@ use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::protocol::AgentMessageEvent;
use codex_protocol::protocol::TurnStartedEvent;
use codex_protocol::protocol::UserMessageEvent;
use core_test_support::PathExt;
use core_test_support::responses::mount_models_once;
use pretty_assertions::assert_eq;
use std::time::Duration;
@@ -236,7 +237,7 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
let temp_dir = tempdir().expect("tempdir");
let mut config = test_config();
config.codex_home = temp_dir.path().join("codex-home");
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
let manager = ThreadManager::with_models_provider_and_home_for_tests(
@@ -275,7 +276,7 @@ async fn new_uses_configured_openai_provider_for_model_refresh() {
let temp_dir = tempdir().expect("tempdir");
let mut config = test_config();
config.codex_home = temp_dir.path().join("codex-home");
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
config.model_catalog = None;
config
@@ -408,7 +409,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
let temp_dir = tempdir().expect("tempdir");
let mut config = test_config();
config.codex_home = temp_dir.path().join("codex-home");
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
let auth_manager =
@@ -505,7 +506,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
let temp_dir = tempdir().expect("tempdir");
let mut config = test_config();
config.codex_home = temp_dir.path().join("codex-home");
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
let auth_manager =
@@ -591,7 +592,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
let temp_dir = tempdir().expect("tempdir");
let mut config = test_config();
config.codex_home = temp_dir.path().join("codex-home");
config.cwd = config.codex_home.clone();
config.cwd = config.codex_home.abs();
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
let auth_manager =
@@ -89,7 +89,7 @@ impl ToolHandler for ArtifactsHandler {
let result = client
.execute_build(ArtifactBuildRequest {
source: args.source,
cwd: turn.cwd.clone(),
cwd: turn.cwd.to_path_buf(),
timeout: Some(Duration::from_millis(
args.timeout_ms
.unwrap_or(DEFAULT_EXECUTION_TIMEOUT.as_millis() as u64),
@@ -221,7 +221,7 @@ fn default_runtime_manager(codex_home: std::path::PathBuf) -> ArtifactRuntimeMan
async fn emit_exec_begin(session: &Session, turn: &TurnContext, call_id: &str) {
let emitter = ToolEmitter::shell(
vec![ARTIFACTS_TOOL_NAME.to_string()],
turn.cwd.clone(),
turn.cwd.to_path_buf(),
ExecCommandSource::Agent,
/*freeform*/ true,
);
@@ -247,7 +247,7 @@ async fn emit_exec_end(
};
let emitter = ToolEmitter::shell(
vec![ARTIFACTS_TOOL_NAME.to_string()],
turn.cwd.clone(),
turn.cwd.to_path_buf(),
ExecCommandSource::Agent,
/*freeform*/ true,
);
+2 -2
View File
@@ -61,7 +61,7 @@ async fn emit_js_repl_exec_begin(
) {
let emitter = ToolEmitter::shell(
vec!["js_repl".to_string()],
turn.cwd.clone(),
turn.cwd.to_path_buf(),
ExecCommandSource::Agent,
/*freeform*/ false,
);
@@ -80,7 +80,7 @@ async fn emit_js_repl_exec_end(
let exec_output = build_js_repl_exec_output(output, error, duration);
let emitter = ToolEmitter::shell(
vec!["js_repl".to_string()],
turn.cwd.clone(),
turn.cwd.to_path_buf(),
ExecCommandSource::Agent,
/*freeform*/ false,
);
@@ -77,7 +77,7 @@ async fn emit_js_repl_exec_end_sends_event() {
assert_eq!(event.call_id, "call-1");
assert_eq!(event.turn_id, turn.sub_id);
assert_eq!(event.command, vec!["js_repl".to_string()]);
assert_eq!(event.cwd, turn.cwd);
assert_eq!(event.cwd, turn.cwd.to_path_buf());
assert_eq!(event.source, ExecCommandSource::Agent);
assert_eq!(event.interaction_input, None);
assert_eq!(event.stdout, "hello");
@@ -39,6 +39,7 @@ use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::user_input::UserInput;
use core_test_support::TempDirExt;
use pretty_assertions::assert_eq;
use serde::Deserialize;
use serde_json::json;
@@ -2236,7 +2237,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
..ShellEnvironmentPolicy::default()
};
let temp_dir = tempfile::tempdir().expect("temp dir");
turn.cwd = temp_dir.path().to_path_buf();
turn.cwd = temp_dir.abs();
turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo"));
let sandbox_policy = pick_allowed_sandbox_policy(
&turn.config.permissions.sandbox_policy,
+1 -1
View File
@@ -1050,7 +1050,7 @@ impl JsReplManager {
"--experimental-vm-modules".to_string(),
kernel_path.to_string_lossy().to_string(),
],
cwd: turn.cwd.clone(),
cwd: turn.cwd.to_path_buf(),
env,
additional_permissions: None,
};
+20 -18
View File
@@ -14,6 +14,8 @@ use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ImageDetail;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::openai_models::InputModality;
use core_test_support::PathBufExt;
use core_test_support::TempDirExt;
use pretty_assertions::assert_eq;
use std::fs;
use std::path::Path;
@@ -739,7 +741,7 @@ async fn interrupt_active_exec_stops_aborted_kernel_before_later_exec() -> anyho
let dir = tempdir()?;
let (session, mut turn) = make_session_and_context().await;
turn.cwd = dir.path().to_path_buf();
turn.cwd = dir.abs();
set_danger_full_access(&mut turn);
let session = Arc::new(session);
let turn = Arc::new(turn);
@@ -1017,7 +1019,7 @@ async fn js_repl_waits_for_unawaited_tool_calls_before_completion() -> anyhow::R
let marker = turn
.cwd
.join(format!("js-repl-unawaited-marker-{}.txt", Uuid::new_v4()));
.join(format!("js-repl-unawaited-marker-{}.txt", Uuid::new_v4()))?;
let marker_json = serde_json::to_string(&marker.to_string_lossy().to_string())?;
let result = manager
.execute(
@@ -1062,10 +1064,10 @@ async fn js_repl_persisted_tool_helpers_work_across_cells() -> anyhow::Result<()
let global_marker = turn
.cwd
.join(format!("js-repl-global-helper-{}.txt", Uuid::new_v4()));
.join(format!("js-repl-global-helper-{}.txt", Uuid::new_v4()))?;
let lexical_marker = turn
.cwd
.join(format!("js-repl-lexical-helper-{}.txt", Uuid::new_v4()));
.join(format!("js-repl-lexical-helper-{}.txt", Uuid::new_v4()))?;
let global_marker_json = serde_json::to_string(&global_marker.to_string_lossy().to_string())?;
let lexical_marker_json = serde_json::to_string(&lexical_marker.to_string_lossy().to_string())?;
@@ -2101,7 +2103,7 @@ async fn js_repl_prefers_env_node_module_dirs_over_config() -> anyhow::Result<()
"CODEX_JS_REPL_NODE_MODULE_DIRS".to_string(),
env_base.path().to_string_lossy().to_string(),
);
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
vec![config_base.path().to_path_buf()],
@@ -2145,7 +2147,7 @@ async fn js_repl_resolves_from_first_config_dir() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
vec![
@@ -2189,7 +2191,7 @@ async fn js_repl_falls_back_to_cwd_node_modules() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
vec![config_base.path().to_path_buf()],
@@ -2230,7 +2232,7 @@ async fn js_repl_accepts_node_modules_dir_entries() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
vec![base_dir.path().join("node_modules")],
@@ -2284,7 +2286,7 @@ async fn js_repl_supports_relative_file_imports() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2331,7 +2333,7 @@ async fn js_repl_supports_absolute_file_imports() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2385,7 +2387,7 @@ async fn js_repl_imported_local_files_can_access_repl_globals() -> anyhow::Resul
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2429,7 +2431,7 @@ async fn js_repl_reimports_local_files_after_edit() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2485,7 +2487,7 @@ async fn js_repl_reimports_local_files_after_fixing_failure() -> anyhow::Result<
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2563,7 +2565,7 @@ async fn js_repl_local_files_expose_node_like_import_meta() -> anyhow::Result<()
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2648,7 +2650,7 @@ async fn js_repl_local_files_reject_static_bare_imports() -> anyhow::Result<()>
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2693,7 +2695,7 @@ async fn js_repl_rejects_unsupported_file_specifiers() -> anyhow::Result<()> {
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2795,7 +2797,7 @@ async fn js_repl_blocks_sensitive_builtin_imports_from_local_files() -> anyhow::
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.path().to_path_buf();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
@@ -2845,7 +2847,7 @@ async fn js_repl_local_files_do_not_escape_node_module_search_roots() -> anyhow:
turn.shell_environment_policy
.r#set
.remove("CODEX_JS_REPL_NODE_MODULE_DIRS");
turn.cwd = cwd_dir.clone();
turn.cwd = cwd_dir.abs();
turn.js_repl = Arc::new(JsReplHandle::with_node_path(
turn.config.js_repl_node_path.clone(),
Vec::new(),
+1 -1
View File
@@ -379,7 +379,7 @@ impl NetworkApprovalService {
approval_id,
/*approval_id*/ None,
prompt_command,
turn_context.cwd.clone(),
turn_context.cwd.to_path_buf(),
Some(prompt_reason),
Some(network_approval_context.clone()),
/*proposed_execpolicy_amendment*/ None,
+1 -1
View File
@@ -530,7 +530,7 @@ async fn dispatch_after_tool_use_hook(
.hooks()
.dispatch(HookPayload {
session_id: session.conversation_id,
cwd: turn.cwd.clone(),
cwd: turn.cwd.to_path_buf(),
client: turn.app_server_client_name.clone(),
triggered_at: chrono::Utc::now(),
hook_event: HookEvent::AfterToolUse {
@@ -159,7 +159,7 @@ pub(super) async fn try_run_zsh_fork(
network: sandbox_network,
windows_sandbox_level,
arg0,
sandbox_policy_cwd: ctx.turn.cwd.clone(),
sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(),
macos_seatbelt_profile_extensions: ctx
.turn
.config
@@ -263,7 +263,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
network: exec_request.network.clone(),
windows_sandbox_level: exec_request.windows_sandbox_level,
arg0: exec_request.arg0.clone(),
sandbox_policy_cwd: ctx.turn.cwd.clone(),
sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(),
macos_seatbelt_profile_extensions: ctx
.turn
.config
@@ -160,7 +160,7 @@ impl UnifiedExecProcessManager {
let cwd = request
.workdir
.clone()
.unwrap_or_else(|| context.turn.cwd.clone());
.unwrap_or_else(|| context.turn.cwd.to_path_buf());
let process = self
.open_session_with_sandbox(&request, cwd.clone(), context)
.await;