Spread AbsolutePathBuf (#17792)

Mechanical change to promote absolute paths through code.
This commit is contained in:
pakrym-oai
2026-04-14 14:26:10 -07:00
committed by GitHub
parent dae56994da
commit dd1321d11b
166 changed files with 1638 additions and 1214 deletions
+34 -42
View File
@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Debug;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
@@ -650,7 +649,7 @@ impl Codex {
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name,
@@ -1132,7 +1131,7 @@ fn local_time_context() -> (String, String) {
async fn thread_title_from_state_db(
state_db: Option<&state_db::StateDbHandle>,
codex_home: &Path,
codex_home: &AbsolutePathBuf,
conversation_id: ThreadId,
) -> Option<String> {
if let Some(metadata) = state_db
@@ -1189,7 +1188,7 @@ pub(crate) struct SessionConfiguration {
/// the process-wide current working directory.
cwd: AbsolutePathBuf,
/// Directory containing all Codex state for this session.
codex_home: PathBuf,
codex_home: AbsolutePathBuf,
/// Optional user-facing name for the thread, updated during the session.
thread_name: Option<String>,
@@ -1208,7 +1207,7 @@ pub(crate) struct SessionConfiguration {
}
impl SessionConfiguration {
pub(crate) fn codex_home(&self) -> &PathBuf {
pub(crate) fn codex_home(&self) -> &AbsolutePathBuf {
&self.codex_home
}
@@ -1220,7 +1219,7 @@ impl SessionConfiguration {
approval_policy: self.approval_policy.value(),
approvals_reviewer: self.approvals_reviewer,
sandbox_policy: self.sandbox_policy.get().clone(),
cwd: self.cwd.to_path_buf(),
cwd: self.cwd.clone(),
ephemeral: self.original_config_do_not_use.ephemeral,
reasoning_effort: self.collaboration_mode.reasoning_effort(),
personality: self.personality,
@@ -1471,7 +1470,7 @@ impl Session {
per_turn_config
}
pub(crate) async fn codex_home(&self) -> PathBuf {
pub(crate) async fn codex_home(&self) -> AbsolutePathBuf {
let state = self.state.lock().await;
state.session_configuration.codex_home().clone()
}
@@ -1572,7 +1571,7 @@ impl Session {
conversation_id.to_string(),
&session_source,
sub_id.clone(),
cwd.to_path_buf(),
cwd.clone(),
session_configuration.sandbox_policy.get(),
session_configuration.windows_sandbox_level,
));
@@ -1927,9 +1926,9 @@ impl Session {
tx
} else {
ShellSnapshot::start_snapshotting(
config.codex_home.to_path_buf(),
config.codex_home.clone(),
conversation_id,
session_configuration.cwd.to_path_buf(),
session_configuration.cwd.clone(),
&mut default_shell,
session_telemetry.clone(),
)
@@ -2133,7 +2132,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.to_path_buf(),
cwd: session_configuration.cwd.clone(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
history_log_id,
history_entry_count,
@@ -2492,9 +2491,9 @@ impl Session {
fn maybe_refresh_shell_snapshot_for_cwd(
&self,
previous_cwd: &Path,
next_cwd: &Path,
codex_home: &Path,
previous_cwd: &AbsolutePathBuf,
next_cwd: &AbsolutePathBuf,
codex_home: &AbsolutePathBuf,
session_source: &SessionSource,
) {
if previous_cwd == next_cwd {
@@ -2513,9 +2512,9 @@ impl Session {
}
ShellSnapshot::refresh_snapshot(
codex_home.to_path_buf(),
codex_home.clone(),
self.conversation_id,
next_cwd.to_path_buf(),
next_cwd.clone(),
self.services.user_shell.as_ref().clone(),
self.services.shell_snapshot_tx.clone(),
self.services.session_telemetry.clone(),
@@ -2779,14 +2778,6 @@ impl Session {
}
};
let config_toml_path = match AbsolutePathBuf::try_from(config_toml_path) {
Ok(path) => path,
Err(err) => {
warn!("failed to resolve user config path while reloading layer: {err}");
return;
}
};
let mut state = self.state.lock().await;
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
config.config_layer_stack = config
@@ -3184,7 +3175,7 @@ impl Session {
call_id: String,
approval_id: Option<String>,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
reason: Option<String>,
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
@@ -5395,9 +5386,12 @@ mod handlers {
cwds: Vec<PathBuf>,
force_reload: bool,
) {
let cwds = if cwds.is_empty() {
let default_cwd = {
let state = sess.state.lock().await;
vec![state.session_configuration.cwd.to_path_buf()]
state.session_configuration.cwd.to_path_buf()
};
let cwds = if cwds.is_empty() {
vec![default_cwd]
} else {
cwds
};
@@ -5412,14 +5406,13 @@ mod handlers {
let cwd_abs = match AbsolutePathBuf::relative_to_current_dir(cwd.as_path()) {
Ok(path) => path,
Err(err) => {
let message = err.to_string();
let cwd_for_entry = cwd.clone();
let error_path = cwd.clone();
skills.push(SkillsListEntry {
cwd: cwd_for_entry.clone(),
cwd,
skills: Vec::new(),
errors: vec![SkillErrorInfo {
path: cwd_for_entry,
message,
path: error_path,
message: err.to_string(),
}],
});
continue;
@@ -5436,14 +5429,13 @@ mod handlers {
{
Ok(config_layer_stack) => config_layer_stack,
Err(err) => {
let message = err.to_string();
let cwd_for_entry = cwd.clone();
let error_path = cwd.clone();
skills.push(SkillsListEntry {
cwd: cwd_for_entry.clone(),
cwd,
skills: Vec::new(),
errors: vec![SkillErrorInfo {
path: cwd_for_entry,
message,
path: error_path,
message: err.to_string(),
}],
});
continue;
@@ -5456,7 +5448,7 @@ mod handlers {
)
.await;
let skills_input = crate::SkillsLoadInput::new(
cwd_abs,
cwd_abs.clone(),
effective_skill_roots,
config_layer_stack,
config.bundled_skills_enabled(),
@@ -5870,7 +5862,7 @@ mod handlers {
sess.maybe_emit_unknown_model_warning_for_turn(turn_context.as_ref())
.await;
sess.refresh_mcp_servers_if_requested(&turn_context).await;
match resolve_review_request(review_request, turn_context.cwd.as_path()) {
match resolve_review_request(review_request, &turn_context.cwd) {
Ok(resolved) => {
spawn_review_thread(
Arc::clone(sess),
@@ -5986,7 +5978,7 @@ async fn spawn_review_thread(
sess.conversation_id.to_string(),
&session_source,
review_turn_id.clone(),
parent_turn_context.cwd.to_path_buf(),
parent_turn_context.cwd.clone(),
parent_turn_context.sandbox_policy.get(),
parent_turn_context.windows_sandbox_level,
));
@@ -6501,7 +6493,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.to_path_buf(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: stop_hook_permission_mode,
@@ -6551,7 +6543,7 @@ pub(crate) async fn run_turn(
.hooks()
.dispatch(HookPayload {
session_id: sess.conversation_id,
cwd: turn_context.cwd.to_path_buf(),
cwd: turn_context.cwd.clone(),
client: turn_context.app_server_client_name.clone(),
triggered_at: chrono::Utc::now(),
hook_event: HookEvent::AfterAgent {
+1 -1
View File
@@ -571,7 +571,7 @@ async fn handle_patch_approval(
new_guardian_review_id(),
GuardianApprovalRequest::ApplyPatch {
id: approval_id.clone(),
cwd: parent_ctx.cwd.to_path_buf(),
cwd: parent_ctx.cwd.clone(),
files,
patch,
},
+4 -3
View File
@@ -23,9 +23,10 @@ use codex_protocol::request_permissions::RequestPermissionsResponse;
use codex_protocol::request_user_input::RequestUserInputAnswer;
use codex_protocol::request_user_input::RequestUserInputEvent;
use codex_protocol::request_user_input::RequestUserInputQuestion;
use core_test_support::PathBufExt;
use core_test_support::test_path_buf;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::watch;
@@ -282,7 +283,7 @@ async fn handle_exec_approval_uses_call_id_for_guardian_review_and_approval_id_f
approval_id: Some("callback-approval-1".to_string()),
turn_id: "child-turn-1".to_string(),
command: vec!["rm".to_string(), "-rf".to_string(), "tmp".to_string()],
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
reason: Some("unsafe subcommand".to_string()),
network_approval_context: None,
proposed_execpolicy_amendment: None,
@@ -313,7 +314,7 @@ async fn handle_exec_approval_uses_call_id_for_guardian_review_and_approval_id_f
let expected_action = GuardianAssessmentAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -rf tmp".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
assert!(!assessment_event.id.is_empty());
assert_eq!(
+9 -9
View File
@@ -1919,7 +1919,7 @@ async fn set_rate_limits_retains_previous_credits() {
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -2021,7 +2021,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -2373,7 +2373,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -2636,7 +2636,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -2740,7 +2740,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -3586,7 +3586,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.to_path_buf(),
codex_home: config.codex_home.clone(),
thread_name: None,
original_config_do_not_use: Arc::clone(&config),
metrics_service_name: None,
@@ -4121,7 +4121,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
let turn_context = Arc::new(turn_context);
let call_id = "ig_history_records_message";
let expected_saved_path = crate::stream_events_utils::image_generation_artifact_path(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session.conversation_id.to_string(),
call_id,
);
@@ -4145,7 +4145,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
let history = session.clone_history().await;
let image_output_path = crate::stream_events_utils::image_generation_artifact_path(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session.conversation_id.to_string(),
"<image_id>",
);
@@ -4173,7 +4173,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
let turn_context = Arc::new(turn_context);
let call_id = "ig_history_no_message";
let expected_saved_path = crate::stream_events_utils::image_generation_artifact_path(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session.conversation_id.to_string(),
call_id,
);
+2 -1
View File
@@ -24,6 +24,7 @@ use codex_protocol::protocol::ThreadMemoryMode;
use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::W3cTraceContext;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use rmcp::model::ReadResourceRequestParams;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -40,7 +41,7 @@ pub struct ThreadConfigSnapshot {
pub approval_policy: AskForApproval,
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox_policy: SandboxPolicy,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub ephemeral: bool,
pub reasoning_effort: Option<ReasoningEffort>,
pub personality: Option<Personality>,
+4 -6
View File
@@ -272,7 +272,7 @@ pub struct Config {
pub user_instructions: Option<String>,
/// Path to the global AGENTS file loaded into `user_instructions`.
pub user_instructions_path: Option<PathBuf>,
pub user_instructions_path: Option<AbsolutePathBuf>,
/// Base instructions override.
pub base_instructions: Option<String>,
@@ -1578,7 +1578,6 @@ impl Config {
};
let memories_root = memory_root(&codex_home);
std::fs::create_dir_all(&memories_root)?;
let memories_root = AbsolutePathBuf::from_absolute_path(&memories_root)?;
if !additional_writable_roots
.iter()
.any(|existing| existing == &memories_root)
@@ -2210,11 +2209,10 @@ impl Config {
Ok(config)
}
fn load_instructions(codex_dir: Option<&Path>) -> Option<LoadedUserInstructions> {
fn load_instructions(codex_dir: Option<&AbsolutePathBuf>) -> Option<LoadedUserInstructions> {
let base = codex_dir?;
for candidate in [LOCAL_PROJECT_DOC_FILENAME, DEFAULT_PROJECT_DOC_FILENAME] {
let mut path = base.to_path_buf();
path.push(candidate);
let path = base.join(candidate);
if let Ok(contents) = std::fs::read_to_string(&path) {
let trimmed = contents.trim();
if !trimmed.is_empty() {
@@ -2297,7 +2295,7 @@ impl Config {
struct LoadedUserInstructions {
contents: String,
path: PathBuf,
path: AbsolutePathBuf,
}
pub(crate) fn uses_deprecated_instructions_file(config_layer_stack: &ConfigLayerStack) -> bool {
+6 -6
View File
@@ -221,7 +221,7 @@ pub async fn process_exec_tool_call(
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
sandbox_cwd: &AbsolutePathBuf,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_legacy_landlock: bool,
stdout_stream: Option<StdoutStream>,
@@ -247,7 +247,7 @@ pub fn build_exec_request(
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
sandbox_cwd: &AbsolutePathBuf,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_legacy_landlock: bool,
) -> Result<ExecRequest> {
@@ -845,7 +845,7 @@ async fn exec(
program: PathBuf::from(program),
args: args.into(),
arg0: arg0_ref,
cwd: cwd.to_path_buf(),
cwd,
network_sandbox_policy,
// The environment already has attempt-scoped proxy settings from
// apply_to_env_for_attempt above. Passing network here would reapply
@@ -881,7 +881,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason(
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
sandbox_policy_cwd: &AbsolutePathBuf,
windows_sandbox_level: WindowsSandboxLevel,
) -> Option<String> {
if windows_sandbox_level == WindowsSandboxLevel::Elevated {
@@ -912,7 +912,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
sandbox_policy_cwd: &AbsolutePathBuf,
windows_sandbox_level: WindowsSandboxLevel,
) -> std::result::Result<Option<WindowsSandboxFilesystemOverrides>, String> {
if sandbox != SandboxType::WindowsRestrictedToken
@@ -1048,7 +1048,7 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
sandbox_policy_cwd: &AbsolutePathBuf,
use_windows_elevated_backend: bool,
) -> std::result::Result<Option<WindowsSandboxFilesystemOverrides>, String> {
if sandbox != SandboxType::WindowsRestrictedToken || !use_windows_elevated_backend {
+20 -22
View File
@@ -1,6 +1,8 @@
use super::*;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_sandboxing::SandboxType;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::time::Duration;
@@ -369,7 +371,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
&sandbox_policy,
&FileSystemSandboxPolicy::from(&sandbox_policy),
NetworkSandboxPolicy::Enabled,
cwd.as_path(),
&cwd,
&None,
/*use_legacy_landlock*/ false,
/*stdout_stream*/ None,
@@ -436,7 +438,7 @@ fn windows_restricted_token_rejects_network_only_restrictions() {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
};
let file_system_policy = FileSystemSandboxPolicy::unrestricted();
let sandbox_policy_cwd = std::env::current_dir().expect("cwd");
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
@@ -457,7 +459,7 @@ fn windows_restricted_token_rejects_network_only_restrictions() {
fn windows_restricted_token_allows_legacy_restricted_policies() {
let policy = SandboxPolicy::new_read_only_policy();
let file_system_policy = FileSystemSandboxPolicy::from(&policy);
let sandbox_policy_cwd = std::env::current_dir().expect("cwd");
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
@@ -482,7 +484,7 @@ fn windows_restricted_token_allows_legacy_workspace_write_policies() {
exclude_slash_tmp: true,
};
let file_system_policy = FileSystemSandboxPolicy::from(&policy);
let sandbox_policy_cwd = std::env::current_dir().expect("cwd");
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
@@ -520,7 +522,7 @@ fn windows_elevated_allows_legacy_restricted_read_policies() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
WindowsSandboxLevel::Elevated,
),
None
@@ -561,7 +563,7 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
WindowsSandboxLevel::RestrictedToken,
),
Some(
@@ -605,7 +607,7 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
WindowsSandboxLevel::RestrictedToken,
),
Some(
@@ -618,9 +620,11 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
#[test]
fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
let temp_dir = tempfile::TempDir::new().expect("tempdir");
let cwd = dunce::canonicalize(temp_dir.path()).expect("canonicalize temp dir");
let cwd = dunce::canonicalize(temp_dir.path())
.expect("canonicalize temp dir")
.abs();
let docs = cwd.join("docs");
std::fs::create_dir_all(&docs).expect("create docs");
std::fs::create_dir_all(docs.as_path()).expect("create docs");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
@@ -642,20 +646,14 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Path {
path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)
.expect("absolute docs"),
},
path: codex_protocol::permissions::FileSystemPath::Path { path: docs.clone() },
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
// The legacy workspace-write root already protects top-level `.codex`, so
// the restricted-token overlay only needs the extra read-only docs carveout.
let expected_deny_write_paths = vec![
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)
.expect("absolute docs"),
];
let expected_deny_write_paths = vec![docs];
assert_eq!(
resolve_windows_restricted_token_filesystem_overrides(
@@ -700,7 +698,7 @@ fn windows_elevated_supports_split_restricted_read_roots() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
/*use_windows_elevated_backend*/ true,
),
Ok(Some(WindowsSandboxFilesystemOverrides {
@@ -752,7 +750,7 @@ fn windows_elevated_supports_split_write_read_carveouts() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
/*use_windows_elevated_backend*/ true,
),
Ok(Some(WindowsSandboxFilesystemOverrides {
@@ -806,7 +804,7 @@ fn windows_elevated_rejects_unreadable_split_carveouts() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
WindowsSandboxLevel::Elevated,
),
Some(
@@ -864,7 +862,7 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
temp_dir.path(),
&temp_dir.path().abs(),
WindowsSandboxLevel::Elevated,
),
Some(
@@ -998,7 +996,7 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
&SandboxPolicy::DangerFullAccess,
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
NetworkSandboxPolicy::Enabled,
cwd.as_path(),
&cwd,
&None,
/*use_legacy_landlock*/ false,
/*stdout_stream*/ None,
+7 -11
View File
@@ -1,5 +1,4 @@
use std::path::Path;
use std::path::PathBuf;
use codex_protocol::approvals::GuardianAssessmentAction;
use codex_protocol::approvals::GuardianCommandSource;
@@ -17,7 +16,7 @@ pub(crate) enum GuardianApprovalRequest {
Shell {
id: String,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
justification: Option<String>,
@@ -25,7 +24,7 @@ pub(crate) enum GuardianApprovalRequest {
ExecCommand {
id: String,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
sandbox_permissions: crate::sandboxing::SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
justification: Option<String>,
@@ -37,12 +36,12 @@ pub(crate) enum GuardianApprovalRequest {
source: GuardianCommandSource,
program: String,
argv: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
additional_permissions: Option<PermissionProfile>,
},
ApplyPatch {
id: String,
cwd: PathBuf,
cwd: AbsolutePathBuf,
files: Vec<AbsolutePathBuf>,
patch: String,
},
@@ -151,12 +150,12 @@ fn serialize_command_guardian_action(
fn command_assessment_action(
source: GuardianCommandSource,
command: &[String],
cwd: &Path,
cwd: &AbsolutePathBuf,
) -> GuardianAssessmentAction {
GuardianAssessmentAction::Command {
source,
command: codex_shell_command::parse_command::shlex_join(command),
cwd: cwd.to_path_buf(),
cwd: cwd.clone(),
}
}
@@ -323,10 +322,7 @@ pub(crate) fn guardian_assessment_action(
GuardianApprovalRequest::ApplyPatch { cwd, files, .. } => {
GuardianAssessmentAction::ApplyPatch {
cwd: cwd.clone(),
files: files
.iter()
.map(codex_utils_absolute_path::AbsolutePathBuf::to_path_buf)
.collect(),
files: files.clone(),
}
}
GuardianApprovalRequest::NetworkAccess {
+3 -2
View File
@@ -35,6 +35,7 @@ use crate::rollout::recorder::RolloutRecorder;
use codex_config::types::McpServerConfig;
use codex_features::Feature;
use codex_model_provider_info::ModelProviderInfo;
use codex_utils_absolute_path::AbsolutePathBuf;
use super::GUARDIAN_REVIEW_TIMEOUT;
use super::GUARDIAN_REVIEWER_NAME;
@@ -129,7 +130,7 @@ struct GuardianReviewSessionReuseKey {
base_instructions: Option<String>,
user_instructions: Option<String>,
compact_prompt: Option<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
mcp_servers: Constrained<HashMap<String, McpServerConfig>>,
codex_linux_sandbox_exe: Option<PathBuf>,
main_execve_wrapper_exe: Option<PathBuf>,
@@ -156,7 +157,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.to_path_buf(),
cwd: spawn_config.cwd.clone(),
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(),
+43 -28
View File
@@ -50,7 +50,6 @@ use insta::Settings;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
@@ -155,6 +154,20 @@ fn guardian_snapshot_options() -> ContextSnapshotOptions {
.strip_agents_md_user_context()
}
fn normalize_guardian_snapshot_paths(text: String) -> String {
let platform_path = test_path_buf("/repo/codex-rs/core").display().to_string();
if platform_path == "/repo/codex-rs/core" {
return text;
}
let escaped_platform_path = serde_json::to_string(&platform_path)
.expect("test path should serialize")
.trim_matches('"')
.to_string();
text.replace(&escaped_platform_path, "/repo/codex-rs/core")
.replace(&platform_path, "/repo/codex-rs/core")
}
fn guardian_prompt_text(items: &[codex_protocol::user_input::UserInput]) -> String {
items
.iter()
@@ -220,7 +233,7 @@ async fn build_guardian_prompt_full_mode_preserves_initial_review_format() -> an
GuardianApprovalRequest::Shell {
id: "shell-1".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the reviewed docs fix.".to_string()),
@@ -276,7 +289,7 @@ async fn build_guardian_prompt_delta_mode_preserves_original_numbering() -> anyh
GuardianApprovalRequest::Shell {
id: "shell-2".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the second docs fix.".to_string()),
@@ -314,7 +327,7 @@ async fn build_guardian_prompt_delta_mode_handles_empty_delta() -> anyhow::Resul
GuardianApprovalRequest::Shell {
id: "shell-2".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the second docs fix.".to_string()),
@@ -349,7 +362,7 @@ async fn build_guardian_prompt_stale_delta_cursor_falls_back_to_full_prompt() ->
GuardianApprovalRequest::Shell {
id: "shell-3".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the docs fix.".to_string()),
@@ -434,7 +447,7 @@ async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -
GuardianApprovalRequest::Shell {
id: "shell-4".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push after the compaction.".to_string()),
@@ -566,7 +579,7 @@ fn format_guardian_action_pretty_truncates_large_string_fields() -> serde_json::
let patch = "line\n".repeat(100_000);
let action = GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
files: Vec::new(),
patch: patch.clone(),
};
@@ -622,7 +635,7 @@ fn guardian_approval_request_to_json_renders_mcp_tool_call_shape() -> serde_json
#[test]
fn guardian_assessment_action_redacts_apply_patch_patch_text() {
let cwd = test_path_buf("/tmp");
let cwd = test_path_buf("/tmp").abs();
let file = test_path_buf("/tmp/guardian.txt").abs();
let action = GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
@@ -654,7 +667,7 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() {
};
let apply_patch = GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: test_path_buf("/tmp"),
cwd: test_path_buf("/tmp").abs(),
files: vec![test_path_buf("/tmp/guardian.txt").abs()],
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
.to_string(),
@@ -682,7 +695,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
"review-cancelled-guardian".to_string(),
GuardianApprovalRequest::ApplyPatch {
id: "patch-1".to_string(),
cwd: test_path_buf("/tmp"),
cwd: test_path_buf("/tmp").abs(),
files: vec![test_path_buf("/tmp/guardian.txt").abs()],
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
.to_string(),
@@ -888,7 +901,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
"origin".to_string(),
"guardian-approval-mvp".to_string(),
],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the reviewed docs fix to the repo remote.".to_string()),
@@ -915,11 +928,11 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
settings.bind(|| {
assert_snapshot!(
"codex_core__guardian__tests__guardian_review_request_layout",
context_snapshot::format_labeled_requests_snapshot(
normalize_guardian_snapshot_paths(context_snapshot::format_labeled_requests_snapshot(
"Guardian review request layout",
&[("Guardian Review Request", &request)],
&guardian_snapshot_options(),
)
))
);
});
@@ -935,7 +948,7 @@ async fn build_guardian_prompt_items_includes_parent_session_id() -> anyhow::Res
GuardianApprovalRequest::Shell {
id: "shell-1".to_string(),
command: vec!["git".to_string(), "status".to_string()],
cwd: PathBuf::from("/repo"),
cwd: test_path_buf("/repo").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: None,
@@ -1009,7 +1022,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
let first_request = GuardianApprovalRequest::Shell {
id: "shell-1".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the first docs fix.".to_string()),
@@ -1055,7 +1068,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
"push".to_string(),
"--force-with-lease".to_string(),
],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the second docs fix.".to_string()),
@@ -1097,7 +1110,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
let third_request = GuardianApprovalRequest::Shell {
id: "shell-3".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the third docs fix.".to_string()),
@@ -1193,13 +1206,15 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
"codex_core__guardian__tests__guardian_followup_review_request_layout",
format!(
"{}\n\nshared_prompt_cache_key: {}\nfollowup_contains_first_rationale: {}",
context_snapshot::format_labeled_requests_snapshot(
"Guardian follow-up review request layout",
&[
("Initial Guardian Review Request", &requests[0]),
("Follow-up Guardian Review Request", &requests[1]),
],
&guardian_snapshot_options(),
normalize_guardian_snapshot_paths(
context_snapshot::format_labeled_requests_snapshot(
"Guardian follow-up review request layout",
&[
("Initial Guardian Review Request", &requests[0]),
("Follow-up Guardian Review Request", &requests[1]),
],
&guardian_snapshot_options(),
)
),
first_body["prompt_cache_key"] == second_body["prompt_cache_key"],
second_body.to_string().contains(first_rationale),
@@ -1257,7 +1272,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
GuardianApprovalRequest::Shell {
id: "shell-guardian-error".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Need to push the reviewed docs fix.".to_string()),
@@ -1380,7 +1395,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
let initial_request = GuardianApprovalRequest::Shell {
id: "shell-guardian-1".to_string(),
command: vec!["git".to_string(), "status".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Inspect repo state before proceeding.".to_string()),
@@ -1425,7 +1440,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
let second_request = GuardianApprovalRequest::Shell {
id: "shell-guardian-2".to_string(),
command: vec!["git".to_string(), "diff".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Inspect pending changes before proceeding.".to_string()),
@@ -1433,7 +1448,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
let third_request = GuardianApprovalRequest::Shell {
id: "shell-guardian-3".to_string(),
command: vec!["git".to_string(), "push".to_string()],
cwd: PathBuf::from("/repo/codex-rs/core"),
cwd: test_path_buf("/repo/codex-rs/core").abs(),
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: Some("Inspect whether pushing is safe before proceeding.".to_string()),
+4 -4
View File
@@ -96,7 +96,7 @@ pub(crate) async fn run_pending_session_start_hooks(
let request = codex_hooks::SessionStartRequest {
session_id: sess.conversation_id,
cwd: turn_context.cwd.to_path_buf(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
@@ -124,7 +124,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.to_path_buf(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
@@ -155,7 +155,7 @@ pub(crate) async fn run_post_tool_use_hooks(
let request = PostToolUseRequest {
session_id: sess.conversation_id,
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.to_path_buf(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
@@ -180,7 +180,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.to_path_buf(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
+9 -5
View File
@@ -4,19 +4,19 @@ use std::io::Result;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::path::Path;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use codex_utils_absolute_path::AbsolutePathBuf;
use tokio::fs;
use uuid::Uuid;
pub(crate) const INSTALLATION_ID_FILENAME: &str = "installation_id";
pub(crate) async fn resolve_installation_id(codex_home: &Path) -> Result<String> {
pub(crate) async fn resolve_installation_id(codex_home: &AbsolutePathBuf) -> Result<String> {
let path = codex_home.join(INSTALLATION_ID_FILENAME);
fs::create_dir_all(codex_home).await?;
tokio::task::spawn_blocking(move || {
@@ -67,6 +67,7 @@ pub(crate) async fn resolve_installation_id(codex_home: &Path) -> Result<String>
mod tests {
use super::INSTALLATION_ID_FILENAME;
use super::resolve_installation_id;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use uuid::Uuid;
@@ -77,9 +78,10 @@ mod tests {
#[tokio::test]
async fn resolve_installation_id_generates_and_persists_uuid() {
let codex_home = TempDir::new().expect("create temp dir");
let codex_home_abs = codex_home.path().abs();
let persisted_path = codex_home.path().join(INSTALLATION_ID_FILENAME);
let installation_id = resolve_installation_id(codex_home.path())
let installation_id = resolve_installation_id(&codex_home_abs)
.await
.expect("resolve installation id");
@@ -103,6 +105,7 @@ mod tests {
#[tokio::test]
async fn resolve_installation_id_reuses_existing_uuid() {
let codex_home = TempDir::new().expect("create temp dir");
let codex_home_abs = codex_home.path().abs();
let existing = Uuid::new_v4().to_string().to_uppercase();
std::fs::write(
codex_home.path().join(INSTALLATION_ID_FILENAME),
@@ -110,7 +113,7 @@ mod tests {
)
.expect("write installation id");
let resolved = resolve_installation_id(codex_home.path())
let resolved = resolve_installation_id(&codex_home_abs)
.await
.expect("resolve installation id");
@@ -125,13 +128,14 @@ mod tests {
#[tokio::test]
async fn resolve_installation_id_rewrites_invalid_file_contents() {
let codex_home = TempDir::new().expect("create temp dir");
let codex_home_abs = codex_home.path().abs();
std::fs::write(
codex_home.path().join(INSTALLATION_ID_FILENAME),
"not-a-uuid",
)
.expect("write invalid installation id");
let resolved = resolve_installation_id(codex_home.path())
let resolved = resolve_installation_id(&codex_home_abs)
.await
.expect("resolve installation id");
+3 -3
View File
@@ -8,9 +8,9 @@ use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
use codex_sandboxing::landlock::allow_network_for_proxy;
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use tokio::process::Child;
/// Spawn a shell tool command under the Linux sandbox helper
@@ -25,9 +25,9 @@ use tokio::process::Child;
pub async fn spawn_command_under_linux_sandbox<P>(
codex_linux_sandbox_exe: P,
command: Vec<String>,
command_cwd: PathBuf,
command_cwd: AbsolutePathBuf,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
sandbox_policy_cwd: &AbsolutePathBuf,
use_legacy_landlock: bool,
stdio_policy: StdioPolicy,
network: Option<&NetworkProxy>,
+8 -8
View File
@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use std::time::Instant;
@@ -55,10 +54,10 @@ use codex_protocol::request_user_input::RequestUserInputResponse;
use codex_rmcp_client::ElicitationAction;
use codex_rmcp_client::ElicitationResponse;
use codex_rollout::state_db;
use codex_utils_absolute_path::AbsolutePathBuf;
use rmcp::model::ToolAnnotations;
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
use std::sync::Arc;
use toml_edit::value;
use tracing::Instrument;
@@ -1512,7 +1511,7 @@ async fn maybe_persist_mcp_tool_approval(
}
async fn persist_codex_app_tool_approval(
codex_home: &Path,
codex_home: &AbsolutePathBuf,
connector_id: &str,
tool_name: &str,
) -> anyhow::Result<()> {
@@ -1545,7 +1544,7 @@ async fn persist_custom_mcp_tool_approval(
if !servers.contains_key(server) {
anyhow::bail!("MCP server `{server}` is not configured in config.toml");
}
config.codex_home.to_path_buf()
config.codex_home.clone()
};
ConfigEditsBuilder::new(&config_folder)
@@ -1563,7 +1562,10 @@ async fn persist_custom_mcp_tool_approval(
.await
}
fn project_mcp_tool_approval_config_folder(config: &Config, server: &str) -> Option<PathBuf> {
fn project_mcp_tool_approval_config_folder(
config: &Config,
server: &str,
) -> Option<AbsolutePathBuf> {
config
.config_layer_stack
.layers_high_to_low()
@@ -1582,9 +1584,7 @@ fn project_mcp_tool_approval_config_folder(config: &Config, server: &str) -> Opt
HashMap::<String, codex_config::types::McpServerConfig>::deserialize(value).ok()
})?;
if servers.contains_key(server) {
layer
.config_folder()
.map(|folder| folder.as_path().to_path_buf())
layer.config_folder()
} else {
None
}
+3 -2
View File
@@ -14,6 +14,7 @@ use codex_config::types::McpServerConfig;
use codex_config::types::McpServerToolConfig;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use core_test_support::PathExt;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_response_created;
@@ -1043,7 +1044,7 @@ fn accepted_elicitation_without_content_defaults_to_accept() {
async fn persist_codex_app_tool_approval_writes_tool_override() {
let tmp = tempdir().expect("tempdir");
persist_codex_app_tool_approval(tmp.path(), "calendar", "calendar/list_events")
persist_codex_app_tool_approval(&tmp.path().abs(), "calendar", "calendar/list_events")
.await
.expect("persist approval");
@@ -1216,7 +1217,7 @@ async fn maybe_persist_mcp_tool_approval_writes_project_config_for_project_serve
.await
.expect("trust project");
let config = ConfigBuilder::default()
.codex_home(codex_home)
.codex_home(codex_home.to_path_buf())
.fallback_cwd(Some(project_dir.path().to_path_buf()))
.build()
.await
+2 -1
View File
@@ -96,10 +96,11 @@ mod metrics {
pub(super) const MEMORY_PHASE_TWO_TOKEN_USAGE: &str = "codex.memory.phase2.token_usage";
}
use codex_utils_absolute_path::AbsolutePathBuf;
use std::path::Path;
use std::path::PathBuf;
pub fn memory_root(codex_home: &Path) -> PathBuf {
pub fn memory_root(codex_home: &AbsolutePathBuf) -> AbsolutePathBuf {
codex_home.join("memories")
}
+2 -19
View File
@@ -21,7 +21,6 @@ use codex_protocol::protocol::TokenUsage;
use codex_protocol::user_input::UserInput;
use codex_state::Stage1Output;
use codex_state::StateRuntime;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
@@ -288,16 +287,7 @@ mod agent {
let root = memory_root(&config.codex_home);
let mut agent_config = config.as_ref().clone();
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;
}
}
agent_config.cwd = root;
// Consolidation threads must never feed back into phase-1 memory generation.
agent_config.memories.generate_memories = false;
// Approval policy
@@ -308,14 +298,7 @@ mod agent {
let _ = agent_config.features.disable(Feature::MemoryTool);
// Sandbox policy
let mut writable_roots = Vec::new();
match AbsolutePathBuf::from_absolute_path(agent_config.codex_home.clone()) {
Ok(codex_home) => writable_roots.push(codex_home),
Err(err) => warn!(
"memory phase-2 consolidation could not add codex_home writable root {}: {err}",
agent_config.codex_home.display()
),
}
let writable_roots = vec![agent_config.codex_home.clone()];
// The consolidation agent only needs local codex_home write access and no network.
let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
+4 -1
View File
@@ -6,6 +6,7 @@ use codex_protocol::openai_models::ModelInfo;
use codex_state::Phase2InputSelection;
use codex_state::Stage1Output;
use codex_state::Stage1OutputRef;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_output_truncation::TruncationPolicy;
use codex_utils_output_truncation::truncate_text;
use codex_utils_template::Template;
@@ -231,7 +232,9 @@ pub(super) fn build_stage_one_input_message(
/// Build prompt used for read path. This prompt must be added to the developer instructions. In
/// case of large memory files, the `memory_summary.md` is truncated at
/// [phase_one::MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT].
pub(crate) async fn build_memory_tool_developer_instructions(codex_home: &Path) -> Option<String> {
pub(crate) async fn build_memory_tool_developer_instructions(
codex_home: &AbsolutePathBuf,
) -> Option<String> {
let base_path = memory_root(codex_home);
let memory_summary_path = base_path.join("memory_summary.md");
let memory_summary = fs::read_to_string(&memory_summary_path)
+3 -2
View File
@@ -1,5 +1,6 @@
use super::*;
use codex_models_manager::model_info::model_info_from_slug;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use tempfile::tempdir;
use tokio::fs as tokio_fs;
@@ -56,7 +57,7 @@ fn build_stage_one_input_message_uses_default_limit_when_model_context_window_mi
#[tokio::test]
async fn build_memory_tool_developer_instructions_renders_embedded_template() {
let temp = tempdir().unwrap();
let codex_home = temp.path();
let codex_home = temp.path().abs();
let memories_dir = codex_home.join("memories");
tokio_fs::create_dir_all(&memories_dir).await.unwrap();
tokio_fs::write(
@@ -66,7 +67,7 @@ async fn build_memory_tool_developer_instructions_renders_embedded_template() {
.await
.unwrap();
let instructions = build_memory_tool_developer_instructions(codex_home)
let instructions = build_memory_tool_developer_instructions(&codex_home)
.await
.unwrap();
+6 -3
View File
@@ -10,6 +10,7 @@ use chrono::Utc;
use codex_config::types::DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION;
use codex_protocol::ThreadId;
use codex_state::Stage1Output;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::path::PathBuf;
@@ -17,8 +18,7 @@ use tempfile::tempdir;
#[test]
fn memory_root_uses_shared_global_path() {
let dir = tempdir().expect("tempdir");
let codex_home = dir.path().join("codex");
let codex_home = AbsolutePathBuf::current_dir().expect("cwd").join("codex");
assert_eq!(memory_root(&codex_home), codex_home.join("memories"));
}
@@ -678,7 +678,10 @@ mod phase2 {
.expect("get consolidation thread");
let config_snapshot = subagent.config_snapshot().await;
pretty_assertions::assert_eq!(config_snapshot.approval_policy, AskForApproval::Never);
pretty_assertions::assert_eq!(config_snapshot.cwd, memory_root(&harness.config.codex_home));
pretty_assertions::assert_eq!(
config_snapshot.cwd.as_path(),
memory_root(&harness.config.codex_home).as_path()
);
match config_snapshot.sandbox_policy {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert!(
+3 -3
View File
@@ -26,7 +26,6 @@ use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
@@ -37,6 +36,7 @@ use tokio::io::AsyncReadExt;
use crate::config::Config;
use codex_config::types::HistoryPersistence;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_protocol::ThreadId;
#[cfg(unix)]
@@ -60,8 +60,8 @@ pub struct HistoryEntry {
pub text: String,
}
fn history_filepath(config: &Config) -> PathBuf {
config.codex_home.join(HISTORY_FILENAME).to_path_buf()
fn history_filepath(config: &Config) -> AbsolutePathBuf {
config.codex_home.join(HISTORY_FILENAME)
}
/// Append a `text` entry associated with `conversation_id` to the history file.
+8 -13
View File
@@ -25,8 +25,8 @@ use codex_network_proxy::NetworkProxyState;
use codex_network_proxy::build_config_state;
use codex_network_proxy::normalize_host;
use codex_network_proxy::validate_policy_against_constraints;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -86,17 +86,12 @@ fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec<LayerMtime> {
.iter()
.filter_map(|layer| {
let path = match &layer.name {
ConfigLayerSource::System { file } => Some(file.as_path().to_path_buf()),
ConfigLayerSource::User { file } => Some(file.as_path().to_path_buf()),
ConfigLayerSource::Project { dot_codex_folder } => Some(
dot_codex_folder
.join(CONFIG_TOML_FILE)
.as_path()
.to_path_buf(),
),
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => {
Some(file.as_path().to_path_buf())
ConfigLayerSource::System { file } => Some(file.clone()),
ConfigLayerSource::User { file } => Some(file.clone()),
ConfigLayerSource::Project { dot_codex_folder } => {
Some(dot_codex_folder.join(CONFIG_TOML_FILE))
}
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => Some(file.clone()),
_ => None,
};
path.map(LayerMtime::new)
@@ -265,12 +260,12 @@ fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
#[derive(Clone)]
struct LayerMtime {
path: PathBuf,
path: AbsolutePathBuf,
mtime: Option<std::time::SystemTime>,
}
impl LayerMtime {
fn new(path: PathBuf) -> Self {
fn new(path: AbsolutePathBuf) -> Self {
let mtime = path.metadata().and_then(|m| m.modified()).ok();
Self { path, mtime }
}
@@ -199,7 +199,9 @@ async fn list_tool_suggest_discoverable_plugins_does_not_reload_marketplace_per_
assert_eq!(discoverable_plugins.len(), 1);
assert_eq!(discoverable_plugins[0].id, "slack@openai-curated");
let logs = String::from_utf8(buffer.lock().expect("buffer lock").clone()).expect("utf8 logs");
let logs = String::from_utf8(buffer.lock().expect("buffer lock").clone())
.expect("utf8 logs")
.replace('\\', "/");
assert_eq!(logs.matches("ignoring interface.defaultPrompt").count(), 2);
let normalized_logs = logs.replace('\\', "/");
assert_eq!(
+5 -5
View File
@@ -1,8 +1,8 @@
use codex_git_utils::merge_base_with_head;
use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::ReviewTarget;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_template::Template;
use std::path::Path;
use std::sync::LazyLock;
#[derive(Clone, Debug, PartialEq)]
@@ -38,7 +38,7 @@ static COMMIT_PROMPT_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
pub fn resolve_review_request(
request: ReviewRequest,
cwd: &Path,
cwd: &AbsolutePathBuf,
) -> anyhow::Result<ResolvedReviewRequest> {
let target = request.target;
let prompt = review_prompt(&target, cwd)?;
@@ -53,7 +53,7 @@ pub fn resolve_review_request(
})
}
pub fn review_prompt(target: &ReviewTarget, cwd: &Path) -> anyhow::Result<String> {
pub fn review_prompt(target: &ReviewTarget, cwd: &AbsolutePathBuf) -> anyhow::Result<String> {
match target {
ReviewTarget::UncommittedChanges => Ok(UNCOMMITTED_PROMPT.to_string()),
ReviewTarget::BaseBranch { branch } => {
@@ -161,7 +161,7 @@ mod tests {
sha: "deadbeef".to_string(),
title: None,
},
Path::new("."),
&AbsolutePathBuf::current_dir().expect("cwd"),
)
.expect("commit prompt should render"),
"Review the code changes introduced by commit deadbeef. Provide prioritized, actionable findings."
@@ -176,7 +176,7 @@ mod tests {
sha: "deadbeef".to_string(),
title: Some("Fix bug".to_string()),
},
Path::new("."),
&AbsolutePathBuf::current_dir().expect("cwd"),
)
.expect("commit prompt should render"),
"Review the code changes introduced by commit deadbeef (\"Fix bug\"). Provide prioritized, actionable findings."
+3 -2
View File
@@ -11,6 +11,7 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxType;
use codex_sandboxing::get_platform_sandbox;
use codex_utils_absolute_path::AbsolutePathBuf;
const PATCH_REJECTED_OUTSIDE_PROJECT_REASON: &str =
"writing outside of the project; rejected by user approval settings";
@@ -34,7 +35,7 @@ pub fn assess_patch_safety(
policy: AskForApproval,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
cwd: &Path,
cwd: &AbsolutePathBuf,
windows_sandbox_level: WindowsSandboxLevel,
) -> SafetyCheck {
if action.is_empty() {
@@ -119,7 +120,7 @@ fn patch_rejection_reason(sandbox_policy: &SandboxPolicy) -> &'static str {
fn is_write_patch_constrained_to_writable_paths(
action: &ApplyPatchAction,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
cwd: &Path,
cwd: &AbsolutePathBuf,
) -> bool {
// Normalize a path by removing `.` and resolving `..` without touching the
// filesystem (works even if the file does not exist).
+22 -24
View File
@@ -5,7 +5,7 @@ use codex_protocol::protocol::FileSystemSandboxEntry;
use codex_protocol::protocol::FileSystemSpecialPath;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
@@ -14,14 +14,12 @@ fn test_writable_roots_constraint() {
// Use a temporary directory as our workspace to avoid touching
// the real current working directory.
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let parent = cwd.parent().unwrap().to_path_buf();
let cwd = tmp.path().abs();
let parent = cwd.parent().unwrap();
// Helper to build a singleentry patch that adds a file at `p`.
let make_add_change = |p: PathBuf| {
let p = p.abs();
ApplyPatchAction::new_add_for_test(&p, "".to_string())
};
let make_add_change =
|p: AbsolutePathBuf| ApplyPatchAction::new_add_for_test(&p, "".to_string());
let add_inside = make_add_change(cwd.join("inner.txt"));
let add_outside = make_add_change(parent.join("outside.txt"));
@@ -51,7 +49,7 @@ fn test_writable_roots_constraint() {
// With the parent dir explicitly added as a writable root, the
// outside write should be permitted.
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from(parent).unwrap()],
writable_roots: vec![parent],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
@@ -67,8 +65,8 @@ fn test_writable_roots_constraint() {
#[test]
fn external_sandbox_auto_approves_in_on_request() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let add_inside_path = cwd.join("inner.txt").abs();
let cwd = tmp.path().abs();
let add_inside_path = cwd.join("inner.txt");
let add_inside = ApplyPatchAction::new_add_for_test(&add_inside_path, "".to_string());
let policy = SandboxPolicy::ExternalSandbox {
@@ -94,9 +92,9 @@ fn external_sandbox_auto_approves_in_on_request() {
#[test]
fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let parent = cwd.parent().unwrap().to_path_buf();
let outside_path = parent.join("outside.txt").abs();
let cwd = tmp.path().abs();
let parent = cwd.parent().unwrap();
let outside_path = parent.join("outside.txt");
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
@@ -139,9 +137,9 @@ fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() {
#[test]
fn granular_sandbox_approval_false_rejects_out_of_root_patch() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let parent = cwd.parent().unwrap().to_path_buf();
let outside_path = parent.join("outside.txt").abs();
let cwd = tmp.path().abs();
let parent = cwd.parent().unwrap();
let outside_path = parent.join("outside.txt");
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
@@ -175,8 +173,8 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() {
#[test]
fn read_only_policy_rejects_patch_with_read_only_reason() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let inside_path = cwd.join("inside.txt").abs();
let cwd = tmp.path().abs();
let inside_path = cwd.join("inside.txt");
let action = ApplyPatchAction::new_add_for_test(&inside_path, "".to_string());
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let file_system_sandbox_policy =
@@ -204,9 +202,9 @@ fn read_only_policy_rejects_patch_with_read_only_reason() {
#[test]
fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let cwd = tmp.path().abs();
let blocked_path = cwd.join("blocked.txt");
let blocked_absolute = blocked_path.abs();
let blocked_absolute = blocked_path;
let action = ApplyPatchAction::new_add_for_test(&blocked_absolute, "".to_string());
let sandbox_policy = SandboxPolicy::ExternalSandbox {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
@@ -247,9 +245,9 @@ fn explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox() {
#[test]
fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let cwd = tmp.path().abs();
let blocked_path = cwd.join("docs").join("blocked.txt");
let blocked_absolute = blocked_path.abs();
let blocked_absolute = blocked_path;
let docs_absolute = AbsolutePathBuf::resolve_path_against_base("docs", &cwd);
let action = ApplyPatchAction::new_add_for_test(&blocked_absolute, "".to_string());
let sandbox_policy = SandboxPolicy::ExternalSandbox {
@@ -291,8 +289,8 @@ fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() {
#[test]
fn missing_project_dot_codex_config_requires_approval() {
let tmp = TempDir::new().unwrap();
let cwd = tmp.path().to_path_buf();
let config_path = cwd.join(".codex").join("config.toml").abs();
let cwd = tmp.path().abs();
let config_path = cwd.join(".codex").join("config.toml");
let action = ApplyPatchAction::new_add_for_test(&config_path, "".to_string());
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
+3 -3
View File
@@ -10,16 +10,16 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
use codex_sandboxing::seatbelt::create_seatbelt_command_args_for_policies;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use tokio::process::Child;
pub async fn spawn_command_under_seatbelt(
command: Vec<String>,
command_cwd: PathBuf,
command_cwd: AbsolutePathBuf,
sandbox_policy: &SandboxPolicy,
sandbox_policy_cwd: &Path,
sandbox_policy_cwd: &AbsolutePathBuf,
stdio_policy: StdioPolicy,
network: Option<&NetworkProxy>,
mut env: HashMap<String, String>,
+48 -46
View File
@@ -1,6 +1,5 @@
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
use std::process::Stdio;
use std::sync::Arc;
use std::time::Duration;
@@ -16,6 +15,7 @@ use anyhow::anyhow;
use anyhow::bail;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_utils_absolute_path::AbsolutePathBuf;
use tokio::fs;
use tokio::process::Command;
use tokio::sync::watch;
@@ -25,8 +25,8 @@ use tracing::info_span;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ShellSnapshot {
pub path: PathBuf,
pub cwd: PathBuf,
pub path: AbsolutePathBuf,
pub cwd: AbsolutePathBuf,
}
const SNAPSHOT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -36,9 +36,9 @@ const EXCLUDED_EXPORT_VARS: &[&str] = &["PWD", "OLDPWD"];
impl ShellSnapshot {
pub fn start_snapshotting(
codex_home: PathBuf,
codex_home: AbsolutePathBuf,
session_id: ThreadId,
session_cwd: PathBuf,
session_cwd: AbsolutePathBuf,
shell: &mut Shell,
session_telemetry: SessionTelemetry,
) -> watch::Sender<Option<Arc<ShellSnapshot>>> {
@@ -58,9 +58,9 @@ impl ShellSnapshot {
}
pub fn refresh_snapshot(
codex_home: PathBuf,
codex_home: AbsolutePathBuf,
session_id: ThreadId,
session_cwd: PathBuf,
session_cwd: AbsolutePathBuf,
shell: Shell,
shell_snapshot_tx: watch::Sender<Option<Arc<ShellSnapshot>>>,
session_telemetry: SessionTelemetry,
@@ -76,9 +76,9 @@ impl ShellSnapshot {
}
fn spawn_snapshot_task(
codex_home: PathBuf,
codex_home: AbsolutePathBuf,
session_id: ThreadId,
session_cwd: PathBuf,
session_cwd: AbsolutePathBuf,
snapshot_shell: Shell,
shell_snapshot_tx: watch::Sender<Option<Arc<ShellSnapshot>>>,
session_telemetry: SessionTelemetry,
@@ -87,14 +87,10 @@ impl ShellSnapshot {
tokio::spawn(
async move {
let timer = session_telemetry.start_timer("codex.shell_snapshot.duration_ms", &[]);
let snapshot = ShellSnapshot::try_new(
&codex_home,
session_id,
session_cwd.as_path(),
&snapshot_shell,
)
.await
.map(Arc::new);
let snapshot =
ShellSnapshot::try_new(&codex_home, session_id, &session_cwd, &snapshot_shell)
.await
.map(Arc::new);
let success = snapshot.is_ok();
let success_tag = if success { "true" } else { "false" };
let _ = timer.map(|timer| timer.record(&[("success", success_tag)]));
@@ -110,9 +106,9 @@ impl ShellSnapshot {
}
async fn try_new(
codex_home: &Path,
codex_home: &AbsolutePathBuf,
session_id: ThreadId,
session_cwd: &Path,
session_cwd: &AbsolutePathBuf,
shell: &Shell,
) -> std::result::Result<Self, &'static str> {
// File to store the snapshot
@@ -132,7 +128,7 @@ impl ShellSnapshot {
.join(format!("{session_id}.tmp-{nonce}"));
// Clean the (unlikely) leaked snapshot files.
let codex_home = codex_home.to_path_buf();
let codex_home = codex_home.clone();
let cleanup_session_id = session_id;
tokio::spawn(async move {
if let Err(err) = cleanup_stale_snapshots(&codex_home, cleanup_session_id).await {
@@ -141,24 +137,23 @@ impl ShellSnapshot {
});
// Make the new snapshot.
let temp_path =
match write_shell_snapshot(shell.shell_type.clone(), &temp_path, session_cwd).await {
Ok(path) => {
tracing::info!("Shell snapshot successfully created: {}", path.display());
path
}
Err(err) => {
tracing::warn!(
"Failed to create shell snapshot for {}: {err:?}",
shell.name()
);
return Err("write_failed");
}
};
if let Err(err) =
write_shell_snapshot(shell.shell_type.clone(), &temp_path, session_cwd).await
{
tracing::warn!(
"Failed to create shell snapshot for {}: {err:?}",
shell.name()
);
return Err("write_failed");
}
tracing::info!(
"Shell snapshot successfully created: {}",
temp_path.display()
);
let temp_snapshot = Self {
path: temp_path.clone(),
cwd: session_cwd.to_path_buf(),
cwd: session_cwd.clone(),
};
if let Err(err) = validate_snapshot(shell, &temp_snapshot.path, session_cwd).await {
@@ -175,7 +170,7 @@ impl ShellSnapshot {
Ok(Self {
path,
cwd: session_cwd.to_path_buf(),
cwd: session_cwd.clone(),
})
}
}
@@ -193,9 +188,9 @@ impl Drop for ShellSnapshot {
async fn write_shell_snapshot(
shell_type: ShellType,
output_path: &Path,
cwd: &Path,
) -> Result<PathBuf> {
output_path: &AbsolutePathBuf,
cwd: &AbsolutePathBuf,
) -> Result<()> {
if shell_type == ShellType::PowerShell || shell_type == ShellType::Cmd {
bail!("Shell snapshot not supported yet for {shell_type:?}");
}
@@ -207,7 +202,7 @@ async fn write_shell_snapshot(
if let Some(parent) = output_path.parent() {
let parent_display = parent.display();
fs::create_dir_all(parent)
fs::create_dir_all(&parent)
.await
.with_context(|| format!("Failed to create snapshot parent {parent_display}"))?;
}
@@ -217,10 +212,10 @@ async fn write_shell_snapshot(
.await
.with_context(|| format!("Failed to write snapshot to {snapshot_path}"))?;
Ok(output_path.to_path_buf())
Ok(())
}
async fn capture_snapshot(shell: &Shell, cwd: &Path) -> Result<String> {
async fn capture_snapshot(shell: &Shell, cwd: &AbsolutePathBuf) -> Result<String> {
let shell_type = shell.shell_type.clone();
match shell_type {
ShellType::Zsh => run_shell_script(shell, &zsh_snapshot_script(), cwd).await,
@@ -240,7 +235,11 @@ fn strip_snapshot_preamble(snapshot: &str) -> Result<String> {
Ok(snapshot[start..].to_string())
}
async fn validate_snapshot(shell: &Shell, snapshot_path: &Path, cwd: &Path) -> Result<()> {
async fn validate_snapshot(
shell: &Shell,
snapshot_path: &AbsolutePathBuf,
cwd: &AbsolutePathBuf,
) -> Result<()> {
let snapshot_path_display = snapshot_path.display();
let script = format!("set -e; . \"{snapshot_path_display}\"");
run_script_with_timeout(
@@ -254,7 +253,7 @@ async fn validate_snapshot(shell: &Shell, snapshot_path: &Path, cwd: &Path) -> R
.map(|_| ())
}
async fn run_shell_script(shell: &Shell, script: &str, cwd: &Path) -> Result<String> {
async fn run_shell_script(shell: &Shell, script: &str, cwd: &AbsolutePathBuf) -> Result<String> {
run_script_with_timeout(
shell,
script,
@@ -270,7 +269,7 @@ async fn run_script_with_timeout(
script: &str,
snapshot_timeout: Duration,
use_login_shell: bool,
cwd: &Path,
cwd: &AbsolutePathBuf,
) -> Result<String> {
let args = shell.derive_exec_args(script, use_login_shell);
let shell_name = shell.name();
@@ -489,7 +488,10 @@ $envVars | ForEach-Object {
/// Removes shell snapshots that either lack a matching session rollout file or
/// whose rollouts have not been updated within the retention window.
/// The active session id is exempt from cleanup.
pub async fn cleanup_stale_snapshots(codex_home: &Path, active_session_id: ThreadId) -> Result<()> {
pub async fn cleanup_stale_snapshots(
codex_home: &AbsolutePathBuf,
active_session_id: ThreadId,
) -> Result<()> {
let snapshot_dir = codex_home.join(SNAPSHOT_DIR);
let mut entries = match fs::read_dir(&snapshot_dir).await {
+33 -23
View File
@@ -1,7 +1,10 @@
use super::*;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
#[cfg(unix)]
use std::process::Command;
#[cfg(target_os = "linux")]
@@ -80,7 +83,7 @@ fn assert_posix_snapshot_sections(snapshot: &str) {
async fn get_snapshot(shell_type: ShellType) -> Result<String> {
let dir = tempdir()?;
let path = dir.path().join("snapshot.sh");
write_shell_snapshot(shell_type, &path, dir.path()).await?;
write_shell_snapshot(shell_type, &path.abs(), &dir.path().abs()).await?;
let content = fs::read_to_string(&path).await?;
Ok(content)
}
@@ -194,12 +197,17 @@ async fn try_new_creates_and_deletes_snapshot_file() -> Result<()> {
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
};
let snapshot = ShellSnapshot::try_new(dir.path(), ThreadId::new(), dir.path(), &shell)
.await
.expect("snapshot should be created");
let snapshot = ShellSnapshot::try_new(
&dir.path().abs(),
ThreadId::new(),
&dir.path().abs(),
&shell,
)
.await
.expect("snapshot should be created");
let path = snapshot.path.clone();
assert!(path.exists());
assert_eq!(snapshot.cwd, dir.path().to_path_buf());
assert_eq!(snapshot.cwd, dir.path().abs());
drop(snapshot);
@@ -219,12 +227,14 @@ async fn try_new_uses_distinct_generation_paths() -> Result<()> {
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
};
let initial_snapshot = ShellSnapshot::try_new(dir.path(), session_id, dir.path(), &shell)
.await
.expect("initial snapshot should be created");
let refreshed_snapshot = ShellSnapshot::try_new(dir.path(), session_id, dir.path(), &shell)
.await
.expect("refreshed snapshot should be created");
let initial_snapshot =
ShellSnapshot::try_new(&dir.path().abs(), session_id, &dir.path().abs(), &shell)
.await
.expect("initial snapshot should be created");
let refreshed_snapshot =
ShellSnapshot::try_new(&dir.path().abs(), session_id, &dir.path().abs(), &shell)
.await
.expect("refreshed snapshot should be created");
let initial_path = initial_snapshot.path.clone();
let refreshed_path = refreshed_snapshot.path.clone();
@@ -250,7 +260,7 @@ async fn snapshot_shell_does_not_inherit_stdin() -> Result<()> {
let _stdin_guard = BlockingStdinPipe::install()?;
let dir = tempdir()?;
let home = dir.path();
let home = dir.path().abs();
let read_status_path = home.join("stdin-read-status");
let read_status_display = read_status_path.display();
// Persist the startup `read` exit status so the test can assert whether
@@ -274,7 +284,7 @@ async fn snapshot_shell_does_not_inherit_stdin() -> Result<()> {
&script,
Duration::from_secs(2),
/*use_login_shell*/ true,
home,
&home,
)
.await
.context("run snapshot command")?;
@@ -318,7 +328,7 @@ async fn timed_out_snapshot_shell_is_terminated() -> Result<()> {
&script,
Duration::from_secs(1),
/*use_login_shell*/ true,
dir.path(),
&dir.path().abs(),
)
.await
.expect_err("snapshot shell should time out");
@@ -403,7 +413,7 @@ async fn write_rollout_stub(codex_home: &Path, session_id: ThreadId) -> Result<P
#[tokio::test]
async fn cleanup_stale_snapshots_removes_orphans_and_keeps_live() -> Result<()> {
let dir = tempdir()?;
let codex_home = dir.path();
let codex_home = dir.path().abs();
let snapshot_dir = codex_home.join(SNAPSHOT_DIR);
fs::create_dir_all(&snapshot_dir).await?;
@@ -413,12 +423,12 @@ async fn cleanup_stale_snapshots_removes_orphans_and_keeps_live() -> Result<()>
let orphan_snapshot = snapshot_dir.join(format!("{orphan_session}.456.sh"));
let invalid_snapshot = snapshot_dir.join("not-a-snapshot.txt");
write_rollout_stub(codex_home, live_session).await?;
write_rollout_stub(&codex_home, live_session).await?;
fs::write(&live_snapshot, "live").await?;
fs::write(&orphan_snapshot, "orphan").await?;
fs::write(&invalid_snapshot, "invalid").await?;
cleanup_stale_snapshots(codex_home, ThreadId::new()).await?;
cleanup_stale_snapshots(&codex_home, ThreadId::new()).await?;
assert_eq!(live_snapshot.exists(), true);
assert_eq!(orphan_snapshot.exists(), false);
@@ -430,18 +440,18 @@ async fn cleanup_stale_snapshots_removes_orphans_and_keeps_live() -> Result<()>
#[tokio::test]
async fn cleanup_stale_snapshots_removes_stale_rollouts() -> Result<()> {
let dir = tempdir()?;
let codex_home = dir.path();
let codex_home = dir.path().abs();
let snapshot_dir = codex_home.join(SNAPSHOT_DIR);
fs::create_dir_all(&snapshot_dir).await?;
let stale_session = ThreadId::new();
let stale_snapshot = snapshot_dir.join(format!("{stale_session}.123.sh"));
let rollout_path = write_rollout_stub(codex_home, stale_session).await?;
let rollout_path = write_rollout_stub(&codex_home, stale_session).await?;
fs::write(&stale_snapshot, "stale").await?;
set_file_mtime(&rollout_path, SNAPSHOT_RETENTION + Duration::from_secs(60))?;
cleanup_stale_snapshots(codex_home, ThreadId::new()).await?;
cleanup_stale_snapshots(&codex_home, ThreadId::new()).await?;
assert_eq!(stale_snapshot.exists(), false);
Ok(())
@@ -451,18 +461,18 @@ async fn cleanup_stale_snapshots_removes_stale_rollouts() -> Result<()> {
#[tokio::test]
async fn cleanup_stale_snapshots_skips_active_session() -> Result<()> {
let dir = tempdir()?;
let codex_home = dir.path();
let codex_home = dir.path().abs();
let snapshot_dir = codex_home.join(SNAPSHOT_DIR);
fs::create_dir_all(&snapshot_dir).await?;
let active_session = ThreadId::new();
let active_snapshot = snapshot_dir.join(format!("{active_session}.123.sh"));
let rollout_path = write_rollout_stub(codex_home, active_session).await?;
let rollout_path = write_rollout_stub(&codex_home, active_session).await?;
fs::write(&active_snapshot, "active").await?;
set_file_mtime(&rollout_path, SNAPSHOT_RETENTION + Duration::from_secs(60))?;
cleanup_stale_snapshots(codex_home, active_session).await?;
cleanup_stale_snapshots(&codex_home, active_session).await?;
assert_eq!(active_snapshot.exists(), true);
Ok(())
+2 -1
View File
@@ -1,4 +1,5 @@
use codex_network_proxy::NetworkProxy;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Stdio;
@@ -40,7 +41,7 @@ pub(crate) struct SpawnChildRequest<'a> {
pub program: PathBuf,
pub args: Vec<String>,
pub arg0: Option<&'a str>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub network: Option<&'a NetworkProxy>,
pub stdio_policy: StdioPolicy,
+11 -12
View File
@@ -1,5 +1,3 @@
use std::path::Path;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
@@ -27,6 +25,7 @@ use codex_protocol::models::MessagePhase;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
use codex_rollout::state_db;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_stream_parser::strip_proposed_plan_blocks;
use futures::Future;
use tracing::debug;
@@ -35,10 +34,10 @@ use tracing::instrument;
const GENERATED_IMAGE_ARTIFACTS_DIR: &str = "generated_images";
pub(crate) fn image_generation_artifact_path(
codex_home: &Path,
codex_home: &AbsolutePathBuf,
session_id: &str,
call_id: &str,
) -> PathBuf {
) -> AbsolutePathBuf {
let sanitize = |value: &str| {
let mut sanitized: String = value
.chars()
@@ -104,11 +103,11 @@ pub(crate) fn raw_assistant_output_text_from_item(item: &ResponseItem) -> Option
}
async fn save_image_generation_result(
codex_home: &std::path::Path,
codex_home: &AbsolutePathBuf,
session_id: &str,
call_id: &str,
result: &str,
) -> Result<PathBuf> {
) -> Result<AbsolutePathBuf> {
let bytes = BASE64_STANDARD
.decode(result.trim().as_bytes())
.map_err(|err| {
@@ -361,7 +360,7 @@ pub(crate) async fn handle_non_tool_response_item(
if let TurnItem::ImageGeneration(image_item) = &mut turn_item {
let session_id = sess.conversation_id.to_string();
match save_image_generation_result(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session_id,
&image_item.id,
&image_item.result,
@@ -369,15 +368,15 @@ pub(crate) async fn handle_non_tool_response_item(
.await
{
Ok(path) => {
image_item.saved_path = Some(path.to_string_lossy().into_owned());
image_item.saved_path = Some(path);
let image_output_path = image_generation_artifact_path(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session_id,
"<image_id>",
);
let image_output_dir = image_output_path
.parent()
.unwrap_or(turn_context.config.codex_home.as_path());
.unwrap_or_else(|| turn_context.config.codex_home.clone());
let message: ResponseItem = DeveloperInstructions::new(format!(
"Generated images are saved to {} as {} by default.\nIf you need to use a generated image at another path, copy it and leave the original in place unless the user explicitly asks you to delete it.",
image_output_dir.display(),
@@ -389,13 +388,13 @@ pub(crate) async fn handle_non_tool_response_item(
}
Err(err) => {
let output_path = image_generation_artifact_path(
turn_context.config.codex_home.as_path(),
&turn_context.config.codex_home,
&session_id,
&image_item.id,
);
let output_dir = output_path
.parent()
.unwrap_or(turn_context.config.codex_home.as_path());
.unwrap_or_else(|| turn_context.config.codex_home.clone());
tracing::warn!(
call_id = %image_item.id,
output_dir = %output_dir.display(),
+20 -17
View File
@@ -9,6 +9,7 @@ use codex_protocol::items::TurnItem;
use codex_protocol::models::ContentItem;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::ResponseItem;
use codex_utils_absolute_path::test_support::PathExt;
use pretty_assertions::assert_eq;
fn assistant_output_text(text: &str) -> ResponseItem {
@@ -128,12 +129,12 @@ fn completed_item_defers_mailbox_delivery_for_image_generation_calls() {
#[tokio::test]
async fn save_image_generation_result_saves_base64_to_png_in_codex_home() {
let codex_home = tempfile::tempdir().expect("create codex home");
let expected_path =
image_generation_artifact_path(codex_home.path(), "session-1", "ig_save_base64");
let codex_home = codex_home.path().abs();
let expected_path = image_generation_artifact_path(&codex_home, "session-1", "ig_save_base64");
let _ = std::fs::remove_file(&expected_path);
let saved_path =
save_image_generation_result(codex_home.path(), "session-1", "ig_save_base64", "Zm9v")
save_image_generation_result(&codex_home, "session-1", "ig_save_base64", "Zm9v")
.await
.expect("image should be saved");
@@ -146,8 +147,9 @@ async fn save_image_generation_result_saves_base64_to_png_in_codex_home() {
async fn save_image_generation_result_rejects_data_url_payload() {
let result = "data:image/jpeg;base64,Zm9v";
let codex_home = tempfile::tempdir().expect("create codex home");
let codex_home = codex_home.path().abs();
let err = save_image_generation_result(codex_home.path(), "session-1", "ig_456", result)
let err = save_image_generation_result(&codex_home, "session-1", "ig_456", result)
.await
.expect_err("data url payload should error");
assert!(matches!(err, CodexErr::InvalidRequest(_)));
@@ -156,8 +158,8 @@ async fn save_image_generation_result_rejects_data_url_payload() {
#[tokio::test]
async fn save_image_generation_result_overwrites_existing_file() {
let codex_home = tempfile::tempdir().expect("create codex home");
let existing_path =
image_generation_artifact_path(codex_home.path(), "session-1", "ig_overwrite");
let codex_home = codex_home.path().abs();
let existing_path = image_generation_artifact_path(&codex_home, "session-1", "ig_overwrite");
std::fs::create_dir_all(
existing_path
.parent()
@@ -166,10 +168,9 @@ async fn save_image_generation_result_overwrites_existing_file() {
.expect("create image output dir");
std::fs::write(&existing_path, b"existing").expect("seed existing image");
let saved_path =
save_image_generation_result(codex_home.path(), "session-1", "ig_overwrite", "Zm9v")
.await
.expect("image should be saved");
let saved_path = save_image_generation_result(&codex_home, "session-1", "ig_overwrite", "Zm9v")
.await
.expect("image should be saved");
assert_eq!(saved_path, existing_path);
assert_eq!(std::fs::read(&saved_path).expect("saved file"), b"foo");
@@ -179,13 +180,13 @@ async fn save_image_generation_result_overwrites_existing_file() {
#[tokio::test]
async fn save_image_generation_result_sanitizes_call_id_for_codex_home_output_path() {
let codex_home = tempfile::tempdir().expect("create codex home");
let expected_path = image_generation_artifact_path(codex_home.path(), "session-1", "../ig/..");
let codex_home = codex_home.path().abs();
let expected_path = image_generation_artifact_path(&codex_home, "session-1", "../ig/..");
let _ = std::fs::remove_file(&expected_path);
let saved_path =
save_image_generation_result(codex_home.path(), "session-1", "../ig/..", "Zm9v")
.await
.expect("image should be saved");
let saved_path = save_image_generation_result(&codex_home, "session-1", "../ig/..", "Zm9v")
.await
.expect("image should be saved");
assert_eq!(saved_path, expected_path);
assert_eq!(std::fs::read(&saved_path).expect("saved file"), b"foo");
@@ -195,7 +196,8 @@ async fn save_image_generation_result_sanitizes_call_id_for_codex_home_output_pa
#[tokio::test]
async fn save_image_generation_result_rejects_non_standard_base64() {
let codex_home = tempfile::tempdir().expect("create codex home");
let err = save_image_generation_result(codex_home.path(), "session-1", "ig_urlsafe", "_-8")
let codex_home = codex_home.path().abs();
let err = save_image_generation_result(&codex_home, "session-1", "ig_urlsafe", "_-8")
.await
.expect_err("non-standard base64 should error");
assert!(matches!(err, CodexErr::InvalidRequest(_)));
@@ -204,8 +206,9 @@ async fn save_image_generation_result_rejects_non_standard_base64() {
#[tokio::test]
async fn save_image_generation_result_rejects_non_base64_data_urls() {
let codex_home = tempfile::tempdir().expect("create codex home");
let codex_home = codex_home.path().abs();
let err = save_image_generation_result(
codex_home.path(),
&codex_home,
"session-1",
"ig_svg",
"data:image/svg+xml,<svg/>",
+5 -5
View File
@@ -131,7 +131,7 @@ pub(crate) async fn execute_user_shell_command(
let exec_command = maybe_wrap_shell_lc_with_snapshot(
&display_command,
session_shell.as_ref(),
turn_context.cwd.as_path(),
&turn_context.cwd,
&turn_context.shell_environment_policy.r#set,
&exec_env_map,
);
@@ -149,7 +149,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.to_path_buf(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -218,7 +218,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.to_path_buf(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -242,7 +242,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.to_path_buf(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -286,7 +286,7 @@ pub(crate) async fn execute_user_shell_command(
process_id: None,
turn_id: turn_context.sub_id.clone(),
command: display_command,
cwd: cwd.to_path_buf(),
cwd,
parsed_cmd,
source: ExecCommandSource::UserShell,
interaction_input: None,
+12 -16
View File
@@ -18,8 +18,8 @@ use codex_protocol::protocol::PatchApplyEndEvent;
use codex_protocol::protocol::PatchApplyStatus;
use codex_protocol::protocol::TurnDiffEvent;
use codex_shell_command::parse_command::parse_command;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
@@ -64,7 +64,7 @@ pub(crate) enum ToolEventFailure {
pub(crate) async fn emit_exec_command_begin(
ctx: ToolEventCtx<'_>,
command: &[String],
cwd: &Path,
cwd: &AbsolutePathBuf,
parsed_cmd: &[ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<String>,
@@ -78,7 +78,7 @@ pub(crate) async fn emit_exec_command_begin(
process_id: process_id.map(str::to_owned),
turn_id: ctx.turn.sub_id.clone(),
command: command.to_vec(),
cwd: cwd.to_path_buf(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.to_vec(),
source,
interaction_input,
@@ -90,7 +90,7 @@ pub(crate) async fn emit_exec_command_begin(
pub(crate) enum ToolEmitter {
Shell {
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
freeform: bool,
@@ -101,7 +101,7 @@ pub(crate) enum ToolEmitter {
},
UnifiedExec {
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
process_id: Option<String>,
@@ -111,7 +111,7 @@ pub(crate) enum ToolEmitter {
impl ToolEmitter {
pub fn shell(
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
source: ExecCommandSource,
freeform: bool,
) -> Self {
@@ -134,7 +134,7 @@ impl ToolEmitter {
pub fn unified_exec(
command: &[String],
cwd: PathBuf,
cwd: AbsolutePathBuf,
source: ExecCommandSource,
process_id: Option<String>,
) -> Self {
@@ -163,11 +163,7 @@ impl ToolEmitter {
emit_exec_stage(
ctx,
ExecCommandInput::new(
command,
cwd.as_path(),
parsed_cmd,
*source,
/*interaction_input*/ None,
command, cwd, parsed_cmd, *source, /*interaction_input*/ None,
/*process_id*/ None,
),
stage,
@@ -273,7 +269,7 @@ impl ToolEmitter {
ctx,
ExecCommandInput::new(
command,
cwd.as_path(),
cwd,
parsed_cmd,
*source,
/*interaction_input*/ None,
@@ -365,7 +361,7 @@ impl ToolEmitter {
struct ExecCommandInput<'a> {
command: &'a [String],
cwd: &'a Path,
cwd: &'a AbsolutePathBuf,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -375,7 +371,7 @@ struct ExecCommandInput<'a> {
impl<'a> ExecCommandInput<'a> {
fn new(
command: &'a [String],
cwd: &'a Path,
cwd: &'a AbsolutePathBuf,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -479,7 +475,7 @@ async fn emit_exec_end(
process_id: exec_input.process_id.map(str::to_owned),
turn_id: ctx.turn.sub_id.clone(),
command: exec_input.command.to_vec(),
cwd: exec_input.cwd.to_path_buf(),
cwd: exec_input.cwd.clone(),
parsed_cmd: exec_input.parsed_cmd.to_vec(),
source: exec_input.source,
interaction_input: exec_input.interaction_input.map(str::to_owned),
+2 -2
View File
@@ -60,7 +60,7 @@ async fn emit_js_repl_exec_begin(
) {
let emitter = ToolEmitter::shell(
vec!["js_repl".to_string()],
turn.cwd.to_path_buf(),
turn.cwd.clone(),
ExecCommandSource::Agent,
/*freeform*/ false,
);
@@ -79,7 +79,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.to_path_buf(),
turn.cwd.clone(),
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.to_path_buf());
assert_eq!(event.cwd, turn.cwd);
assert_eq!(event.source, ExecCommandSource::Agent);
assert_eq!(event.interaction_input, None);
assert_eq!(event.stdout, "hello");
+1 -1
View File
@@ -483,7 +483,7 @@ impl ShellHandler {
let source = ExecCommandSource::Agent;
let emitter = ToolEmitter::shell(
exec_params.command.clone(),
exec_params.cwd.to_path_buf(),
exec_params.cwd.clone(),
source,
freeform,
);
@@ -2,6 +2,8 @@ use std::path::PathBuf;
use std::sync::Arc;
use codex_protocol::models::ShellCommandToolCallParams;
use core_test_support::PathBufExt;
use core_test_support::test_path_buf;
use pretty_assertions::assert_eq;
use crate::codex::make_session_and_context;
@@ -125,8 +127,8 @@ async fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_contex
#[test]
fn shell_command_handler_respects_explicit_login_flag() {
let (_tx, shell_snapshot) = watch::channel(Some(Arc::new(ShellSnapshot {
path: PathBuf::from("/tmp/snapshot.sh"),
cwd: PathBuf::from("/tmp"),
path: test_path_buf("/tmp/snapshot.sh").abs(),
cwd: test_path_buf("/tmp").abs(),
})));
let shell = Shell {
shell_type: ShellType::Bash,
@@ -123,7 +123,7 @@ impl ToolHandler for ViewImageHandler {
abs_path.display()
))
})?;
let event_path = abs_path.to_path_buf();
let event_path = abs_path.clone();
let can_request_original_detail = can_request_original_image_detail(&turn.model_info);
let use_original_detail =
+1 -1
View File
@@ -401,7 +401,7 @@ impl NetworkApprovalService {
approval_id,
/*approval_id*/ None,
prompt_command,
turn_context.cwd.to_path_buf(),
turn_context.cwd.clone(),
Some(prompt_reason),
Some(network_approval_context.clone()),
/*proposed_execpolicy_amendment*/ None,
+1 -1
View File
@@ -578,7 +578,7 @@ async fn dispatch_after_tool_use_hook(
.hooks()
.dispatch(HookPayload {
session_id: session.conversation_id,
cwd: turn.cwd.to_path_buf(),
cwd: turn.cwd.clone(),
client: turn.app_server_client_name.clone(),
triggered_at: chrono::Utc::now(),
hook_event: HookEvent::AfterToolUse {
@@ -60,7 +60,7 @@ impl ApplyPatchRuntime {
) -> GuardianApprovalRequest {
GuardianApprovalRequest::ApplyPatch {
id: call_id.to_string(),
cwd: req.action.cwd.to_path_buf(),
cwd: req.action.cwd.clone(),
files: req.file_paths.clone(),
patch: req.action.patch.clone(),
}
@@ -43,7 +43,7 @@ fn guardian_review_request_includes_patch_context() {
.join("guardian-apply-patch-test.txt")
.abs();
let action = ApplyPatchAction::new_add_for_test(&path, "hello".to_string());
let expected_cwd = action.cwd.to_path_buf();
let expected_cwd = action.cwd.clone();
let expected_patch = action.patch.clone();
let request = ApplyPatchRequest {
action,
@@ -108,7 +108,7 @@ fn file_system_sandbox_context_uses_active_attempt() {
network_policy: NetworkSandboxPolicy::Restricted,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: path.as_path(),
sandbox_cwd: &path,
codex_linux_sandbox_exe: None,
use_legacy_landlock: true,
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
@@ -154,7 +154,7 @@ fn no_sandbox_attempt_has_no_file_system_context() {
network_policy: NetworkSandboxPolicy::Enabled,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: path.as_path(),
sandbox_cwd: &path,
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
+1 -2
View File
@@ -12,7 +12,6 @@ use codex_protocol::models::PermissionProfile;
use codex_sandboxing::SandboxCommand;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
pub(crate) mod apply_patch;
pub(crate) mod shell;
@@ -60,7 +59,7 @@ pub(crate) fn build_sandbox_command(
pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
command: &[String],
session_shell: &Shell,
cwd: &Path,
cwd: &AbsolutePathBuf,
explicit_env_overrides: &HashMap<String, String>,
env: &HashMap<String, String>,
) -> Vec<String> {
+48 -41
View File
@@ -1,6 +1,9 @@
use super::*;
use crate::shell::ShellType;
use crate::shell_snapshot::ShellSnapshot;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
use std::process::Command;
@@ -11,8 +14,8 @@ use tokio::sync::watch;
fn shell_with_snapshot(
shell_type: ShellType,
shell_path: &str,
snapshot_path: PathBuf,
snapshot_cwd: PathBuf,
snapshot_path: AbsolutePathBuf,
snapshot_cwd: AbsolutePathBuf,
) -> Shell {
let (_tx, shell_snapshot) = watch::channel(Some(Arc::new(ShellSnapshot {
path: snapshot_path,
@@ -33,8 +36,8 @@ fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -45,7 +48,7 @@ fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -64,8 +67,8 @@ fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -76,7 +79,7 @@ fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -92,8 +95,8 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/zsh".to_string(),
@@ -104,7 +107,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -123,8 +126,8 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell() {
let session_shell = shell_with_snapshot(
ShellType::Sh,
"/bin/sh",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -135,7 +138,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -154,8 +157,8 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -168,7 +171,7 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -188,8 +191,12 @@ fn maybe_wrap_shell_lc_with_snapshot_skips_when_cwd_mismatch() {
let command_cwd = dir.path().join("worktree-b");
std::fs::create_dir_all(&snapshot_cwd).expect("create snapshot cwd");
std::fs::create_dir_all(&command_cwd).expect("create command cwd");
let session_shell =
shell_with_snapshot(ShellType::Zsh, "/bin/zsh", snapshot_path, snapshot_cwd);
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path.abs(),
snapshot_cwd.abs(),
);
let command = vec![
"/bin/bash".to_string(),
"-lc".to_string(),
@@ -199,7 +206,7 @@ fn maybe_wrap_shell_lc_with_snapshot_skips_when_cwd_mismatch() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
&command_cwd,
&command_cwd.abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -215,8 +222,8 @@ fn maybe_wrap_shell_lc_with_snapshot_accepts_dot_alias_cwd() {
let session_shell = shell_with_snapshot(
ShellType::Zsh,
"/bin/zsh",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -228,7 +235,7 @@ fn maybe_wrap_shell_lc_with_snapshot_accepts_dot_alias_cwd() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
&command_cwd,
&command_cwd.abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -251,8 +258,8 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -264,7 +271,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::from([("TEST_ENV_SNAPSHOT".to_string(), "worktree".to_string())]),
);
@@ -293,8 +300,8 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -304,7 +311,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::from([("CODEX_THREAD_ID".to_string(), "nested-thread".to_string())]),
);
@@ -330,8 +337,8 @@ fn maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -341,7 +348,7 @@ fn maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
);
@@ -366,8 +373,8 @@ fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -378,7 +385,7 @@ fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]),
);
@@ -404,8 +411,8 @@ fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -419,7 +426,7 @@ fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::from([(
"OPENAI_API_KEY".to_string(),
@@ -452,8 +459,8 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables() {
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path,
dir.path().to_path_buf(),
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
@@ -467,7 +474,7 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables() {
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
dir.path(),
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::new(),
);
+2 -2
View File
@@ -145,7 +145,7 @@ impl Approvable<ShellRequest> for ShellRuntime {
) -> BoxFuture<'a, ReviewDecision> {
let keys = self.approval_keys(req);
let command = req.command.clone();
let cwd = req.cwd.to_path_buf();
let cwd = req.cwd.clone();
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
let session = ctx.session;
@@ -161,7 +161,7 @@ impl Approvable<ShellRequest> for ShellRuntime {
GuardianApprovalRequest::Shell {
id: call_id,
command,
cwd,
cwd: cwd.clone(),
sandbox_permissions: req.sandbox_permissions,
additional_permissions: req.additional_permissions.clone(),
justification: req.justification.clone(),
@@ -158,7 +158,7 @@ pub(super) async fn try_run_zsh_fork(
network: sandbox_network,
windows_sandbox_level,
arg0,
sandbox_policy_cwd: ctx.turn.cwd.to_path_buf(),
sandbox_policy_cwd: ctx.turn.cwd.clone(),
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
@@ -256,7 +256,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.to_path_buf(),
sandbox_policy_cwd: ctx.turn.cwd.clone(),
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
@@ -386,7 +386,7 @@ impl CoreShellActionProvider {
additional_permissions: Option<PermissionProfile>,
) -> anyhow::Result<PromptDecision> {
let command = join_program_and_argv(program, argv);
let workdir = workdir.to_path_buf();
let workdir = workdir.clone();
let session = self.session.clone();
let turn = self.turn.clone();
let call_id = self.call_id.clone();
@@ -405,7 +405,7 @@ impl CoreShellActionProvider {
source,
program: program.to_string_lossy().into_owned(),
argv: argv.to_vec(),
cwd: workdir,
cwd: workdir.clone(),
additional_permissions,
},
/*retry_reason*/ None,
@@ -422,7 +422,7 @@ impl CoreShellActionProvider {
call_id,
approval_id,
command,
workdir,
workdir.clone(),
/*reason*/ None,
/*network_approval_context*/ None,
/*proposed_execpolicy_amendment*/ None,
@@ -696,7 +696,7 @@ struct CoreShellCommandExecutor {
network: Option<codex_network_proxy::NetworkProxy>,
windows_sandbox_level: WindowsSandboxLevel,
arg0: Option<String>,
sandbox_policy_cwd: PathBuf,
sandbox_policy_cwd: AbsolutePathBuf,
codex_linux_sandbox_exe: Option<PathBuf>,
use_legacy_landlock: bool,
}
@@ -126,7 +126,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
let turn = ctx.turn;
let call_id = ctx.call_id.to_string();
let command = req.command.clone();
let cwd = req.cwd.to_path_buf();
let cwd = req.cwd.clone();
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
let guardian_review_id = ctx.guardian_review_id.clone();
@@ -139,7 +139,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
GuardianApprovalRequest::ExecCommand {
id: call_id,
command,
cwd,
cwd: cwd.clone(),
sandbox_permissions: req.sandbox_permissions,
additional_permissions: req.additional_permissions.clone(),
justification: req.justification.clone(),
@@ -157,7 +157,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
call_id,
/*approval_id*/ None,
command,
cwd,
cwd.clone(),
reason,
ctx.network_approval_context.clone(),
req.exec_approval_requirement
+2 -2
View File
@@ -27,13 +27,13 @@ use codex_sandboxing::SandboxTransformError;
use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::Future;
use futures::future::BoxFuture;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::path::Path;
use std::sync::Arc;
#[derive(Clone, Default, Debug)]
@@ -331,7 +331,7 @@ pub(crate) struct SandboxAttempt<'a> {
pub network_policy: NetworkSandboxPolicy,
pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a Path,
pub(crate) sandbox_cwd: &'a AbsolutePathBuf,
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
+7 -5
View File
@@ -1,7 +1,5 @@
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::RwLock;
@@ -18,6 +16,7 @@ use codex_git_utils::get_head_commit_hash;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_utils_absolute_path::AbsolutePathBuf;
#[derive(Clone, Debug, Default)]
struct WorkspaceGitMetadata {
@@ -112,7 +111,10 @@ fn build_turn_metadata_bag(
}
}
pub async fn build_turn_metadata_header(cwd: &Path, sandbox: Option<&str>) -> Option<String> {
pub async fn build_turn_metadata_header(
cwd: &AbsolutePathBuf,
sandbox: Option<&str>,
) -> Option<String> {
let repo_root = get_git_repo_root(cwd).map(|root| root.to_string_lossy().into_owned());
let (head_commit_hash, associated_remote_urls, has_changes) = tokio::join!(
@@ -146,7 +148,7 @@ pub async fn build_turn_metadata_header(cwd: &Path, sandbox: Option<&str>) -> Op
#[derive(Clone, Debug)]
pub(crate) struct TurnMetadataState {
cwd: PathBuf,
cwd: AbsolutePathBuf,
repo_root: Option<String>,
base_metadata: TurnMetadataBag,
base_header: String,
@@ -160,7 +162,7 @@ impl TurnMetadataState {
session_id: String,
session_source: &SessionSource,
turn_id: String,
cwd: PathBuf,
cwd: AbsolutePathBuf,
sandbox_policy: &SandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel,
) -> Self {
+6 -4
View File
@@ -2,6 +2,8 @@ use super::*;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use serde_json::Value;
use std::collections::HashMap;
use tempfile::TempDir;
@@ -10,7 +12,7 @@ use tokio::process::Command;
#[tokio::test]
async fn build_turn_metadata_header_includes_has_changes_for_clean_repo() {
let temp_dir = TempDir::new().expect("temp dir");
let repo_path = temp_dir.path().join("repo");
let repo_path = temp_dir.path().join("repo").abs();
std::fs::create_dir_all(&repo_path).expect("create repo");
Command::new("git")
@@ -66,7 +68,7 @@ async fn build_turn_metadata_header_includes_has_changes_for_clean_repo() {
#[test]
fn turn_metadata_state_uses_platform_sandbox_tag() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().to_path_buf();
let cwd = temp_dir.path().abs();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let state = TurnMetadataState::new(
@@ -94,7 +96,7 @@ fn turn_metadata_state_uses_platform_sandbox_tag() {
#[test]
fn turn_metadata_state_classifies_subagent_thread_source() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().to_path_buf();
let cwd = temp_dir.path().abs();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let session_source = SessionSource::SubAgent(SubAgentSource::Review);
@@ -117,7 +119,7 @@ fn turn_metadata_state_classifies_subagent_thread_source() {
#[test]
fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().to_path_buf();
let cwd = temp_dir.path().abs();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let state = TurnMetadataState::new(
@@ -1,4 +1,3 @@
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
@@ -23,6 +22,7 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ExecOutputStream;
use codex_utils_absolute_path::AbsolutePathBuf;
pub(crate) const TRAILING_OUTPUT_GRACE: Duration = Duration::from_millis(100);
@@ -110,7 +110,7 @@ pub(crate) fn spawn_exit_watcher(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
process_id: i32,
transcript: Arc<Mutex<HeadTailBuffer>>,
started_at: Instant,
@@ -196,7 +196,7 @@ pub(crate) async fn emit_exec_end_for_unified_exec(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
process_id: Option<String>,
transcript: Arc<Mutex<HeadTailBuffer>>,
fallback_output: String,
@@ -235,7 +235,7 @@ pub(crate) async fn emit_failed_exec_end_for_unified_exec(
turn_ref: Arc<TurnContext>,
call_id: String,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
process_id: Option<String>,
transcript: Arc<Mutex<HeadTailBuffer>>,
message: String,
@@ -256,7 +256,7 @@ impl UnifiedExecProcessManager {
);
let emitter = ToolEmitter::unified_exec(
&request.command,
cwd.to_path_buf(),
cwd.clone(),
ExecCommandSource::UnifiedExecStartup,
Some(request.process_id.to_string()),
);
@@ -322,7 +322,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd.to_path_buf(),
cwd.clone(),
Some(request.process_id.to_string()),
Arc::clone(&transcript),
message.clone(),
@@ -365,7 +365,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd.to_path_buf(),
cwd.clone(),
Some(process_id.to_string()),
Arc::clone(&transcript),
text.clone(),
@@ -639,7 +639,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
command.to_vec(),
cwd.to_path_buf(),
cwd,
process_id,
transcript,
started_at,