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

View File

@@ -140,9 +140,9 @@ use codex_protocol::request_user_input::RequestUserInputAnswer as CoreRequestUse
use codex_protocol::request_user_input::RequestUserInputResponse as CoreRequestUserInputResponse;
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
use codex_shell_command::parse_command::shlex_join;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::oneshot;
@@ -159,7 +159,7 @@ enum CommandExecutionApprovalPresentation {
#[derive(Debug, PartialEq)]
struct CommandExecutionCompletionItem {
command: String,
cwd: PathBuf,
cwd: AbsolutePathBuf,
command_actions: Vec<V2ParsedCommand>,
}
@@ -644,7 +644,7 @@ pub(crate) async fn apply_bespoke_event_handling(
call_id: call_id.clone(),
approval_id,
command,
cwd,
cwd: cwd.to_path_buf(),
reason,
parsed_cmd,
};
@@ -666,7 +666,7 @@ pub(crate) async fn apply_bespoke_event_handling(
let command_actions = parsed_cmd
.iter()
.cloned()
.map(V2ParsedCommand::from)
.map(|parsed| V2ParsedCommand::from_core_with_cwd(parsed, &cwd))
.collect::<Vec<_>>();
let presentation = if let Some(network_approval_context) =
network_approval_context.map(V2NetworkApprovalContext::from)
@@ -1463,7 +1463,7 @@ pub(crate) async fn apply_bespoke_event_handling(
EventMsg::ViewImageToolCall(view_image_event) => {
let item = ThreadItem::ImageView {
id: view_image_event.call_id.clone(),
path: view_image_event.path.to_string_lossy().into_owned(),
path: view_image_event.path.clone(),
};
let started = ItemStartedNotification {
thread_id: conversation_id.to_string(),
@@ -1648,13 +1648,13 @@ pub(crate) async fn apply_bespoke_event_handling(
return;
}
let item_id = exec_command_begin_event.call_id.clone();
let cwd = exec_command_begin_event.cwd.clone();
let command_actions = exec_command_begin_event
.parsed_cmd
.into_iter()
.map(V2ParsedCommand::from)
.map(|parsed| V2ParsedCommand::from_core_with_cwd(parsed, &cwd))
.collect::<Vec<_>>();
let command = shlex_join(&exec_command_begin_event.command);
let cwd = exec_command_begin_event.cwd;
let process_id = exec_command_begin_event.process_id;
let first_start = {
let mut state = thread_state.lock().await;
@@ -1834,7 +1834,8 @@ pub(crate) async fn apply_bespoke_event_handling(
.await
{
Ok(summary) => {
let mut thread = summary_to_thread(summary);
let fallback_cwd = conversation.config_snapshot().await.cwd;
let mut thread = summary_to_thread(summary, &fallback_cwd);
match read_rollout_items_from_rollout(rollout_path.as_path()).await {
Ok(items) => {
thread.turns = build_turns_from_rollout_items(&items);
@@ -2035,7 +2036,7 @@ async fn start_command_execution_item(
turn_id: String,
item_id: String,
command: String,
cwd: PathBuf,
cwd: AbsolutePathBuf,
command_actions: Vec<V2ParsedCommand>,
source: CommandExecutionSource,
outgoing: &ThreadScopedOutgoingMessageSender,
@@ -2078,7 +2079,7 @@ async fn complete_command_execution_item(
turn_id: String,
item_id: String,
command: String,
cwd: PathBuf,
cwd: AbsolutePathBuf,
process_id: Option<String>,
source: CommandExecutionSource,
command_actions: Vec<V2ParsedCommand>,
@@ -3002,6 +3003,8 @@ mod tests {
use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TokenUsageInfo;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use core_test_support::load_default_config_for_test;
use pretty_assertions::assert_eq;
use rmcp::model::Content;
@@ -3054,7 +3057,7 @@ mod tests {
fn command_execution_completion_item(command: &str) -> CommandExecutionCompletionItem {
CommandExecutionCompletionItem {
command: command.to_string(),
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
command_actions: vec![V2ParsedCommand::Unknown {
command: command.to_string(),
}],
@@ -3100,7 +3103,7 @@ mod tests {
"type": "command",
"source": "shell",
"command": format!("rm -f /tmp/{id}.sqlite"),
"cwd": "/tmp",
"cwd": test_path_buf("/tmp"),
}))
.expect("guardian action"),
}
@@ -3146,7 +3149,7 @@ mod tests {
let action = codex_protocol::protocol::GuardianAssessmentAction::Command {
source: codex_protocol::protocol::GuardianCommandSource::Shell,
command: "rm -rf /tmp/example.sqlite".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
let notification = guardian_auto_approval_review_notification(
&conversation_id,
@@ -3189,7 +3192,7 @@ mod tests {
let action = codex_protocol::protocol::GuardianAssessmentAction::Command {
source: codex_protocol::protocol::GuardianCommandSource::Shell,
command: "rm -rf /tmp/example.sqlite".to_string(),
cwd: "/tmp".into(),
cwd: test_path_buf("/tmp").abs(),
};
let notification = guardian_auto_approval_review_notification(
&conversation_id,

View File

@@ -611,15 +611,12 @@ pub(crate) struct CodexMessageProcessorArgs {
}
impl CodexMessageProcessor {
async fn instruction_sources_from_config(config: &Config) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = config.user_instructions_path.iter().cloned().collect();
async fn instruction_sources_from_config(config: &Config) -> Vec<AbsolutePathBuf> {
let mut paths: Vec<AbsolutePathBuf> =
config.user_instructions_path.iter().cloned().collect();
match codex_core::discover_project_doc_paths(config, LOCAL_FS.as_ref()).await {
Ok(project_doc_paths) => {
paths.extend(
project_doc_paths
.into_iter()
.map(|path| path.as_path().to_path_buf()),
);
paths.extend(project_doc_paths);
}
Err(err) => {
tracing::warn!(error = %err, "failed to discover project docs for thread response");
@@ -2162,7 +2159,7 @@ impl CodexMessageProcessor {
&effective_policy,
&effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
sandbox_cwd.as_path(),
&sandbox_cwd,
&codex_linux_sandbox_exe,
use_legacy_landlock,
) {
@@ -3201,7 +3198,7 @@ impl CodexMessageProcessor {
return;
};
let mut thread = summary_to_thread(summary);
let mut thread = summary_to_thread(summary, &self.config.cwd);
self.attach_thread_name(thread_uuid, &mut thread).await;
thread.status = resolve_thread_status(
self.thread_watch_manager
@@ -3284,7 +3281,7 @@ impl CodexMessageProcessor {
config_snapshot.session_source.clone(),
);
builder.model_provider = Some(model_provider.clone());
builder.cwd = config_snapshot.cwd.clone();
builder.cwd = config_snapshot.cwd.to_path_buf();
builder.cli_version = Some(env!("CARGO_PKG_VERSION").to_string());
builder.sandbox_policy = config_snapshot.sandbox_policy.clone();
builder.approval_mode = config_snapshot.approval_policy;
@@ -3509,7 +3506,7 @@ impl CodexMessageProcessor {
message: format!("failed to read unarchived thread: {err}"),
data: None,
})?;
Ok(summary_to_thread(summary))
Ok(summary_to_thread(summary, &self.config.cwd))
}
.await;
@@ -3770,7 +3767,7 @@ impl CodexMessageProcessor {
let conversation_id = summary.conversation_id;
thread_ids.insert(conversation_id);
let thread = summary_to_thread(summary);
let thread = summary_to_thread(summary, &self.config.cwd);
status_ids.push(thread.id.clone());
threads.push((conversation_id, thread));
}
@@ -3914,11 +3911,11 @@ impl CodexMessageProcessor {
}
let mut thread = if let Some(summary) = db_summary {
summary_to_thread(summary)
summary_to_thread(summary, &self.config.cwd)
} else if let Some(rollout_path) = rollout_path.as_ref() {
let fallback_provider = self.config.model_provider_id.as_str();
match read_summary_from_rollout(rollout_path, fallback_provider).await {
Ok(summary) => summary_to_thread(summary),
Ok(summary) => summary_to_thread(summary, &self.config.cwd),
Err(err) => {
self.send_internal_error(
request_id,
@@ -4424,9 +4421,7 @@ impl CodexMessageProcessor {
);
}
let mut config_for_instruction_sources = self.config.as_ref().clone();
if let Ok(cwd) = AbsolutePathBuf::try_from(config_snapshot.cwd.clone()) {
config_for_instruction_sources.cwd = cwd;
}
config_for_instruction_sources.cwd = config_snapshot.cwd.clone();
let instruction_sources =
Self::instruction_sources_from_config(&config_for_instruction_sources).await;
let thread_summary = match load_thread_summary_for_rollout(
@@ -4809,7 +4804,7 @@ impl CodexMessageProcessor {
.await
{
Ok(summary) => {
let mut thread = summary_to_thread(summary);
let mut thread = summary_to_thread(summary, &self.config.cwd);
thread.forked_from_id =
forked_from_id_from_rollout(fork_rollout_path.as_path()).await;
thread
@@ -6348,7 +6343,7 @@ impl CodexMessageProcessor {
)
.await;
let skills_input = codex_core::skills::SkillsLoadInput::new(
cwd_abs,
cwd_abs.clone(),
effective_skill_roots,
config_layer_stack,
config.bundled_skills_enabled(),
@@ -7615,7 +7610,7 @@ impl CodexMessageProcessor {
if let Some(rollout_path) = review_thread.rollout_path() {
match read_summary_from_rollout(rollout_path.as_path(), fallback_provider).await {
Ok(summary) => {
let mut thread = summary_to_thread(summary);
let mut thread = summary_to_thread(summary, &self.config.cwd);
self.thread_watch_manager
.upsert_thread_silently(thread.clone())
.await;
@@ -8817,7 +8812,7 @@ fn collect_resume_override_mismatches(
}
if let Some(requested_cwd) = request.cwd.as_deref() {
let requested_cwd_path = std::path::PathBuf::from(requested_cwd);
if requested_cwd_path != config_snapshot.cwd {
if requested_cwd_path != config_snapshot.cwd.as_path() {
mismatch_details.push(format!(
"cwd requested={} active={}",
requested_cwd_path.display(),
@@ -9601,7 +9596,7 @@ async fn load_thread_summary_for_rollout(
) -> std::result::Result<Thread, String> {
let mut thread = read_summary_from_rollout(rollout_path, fallback_provider)
.await
.map(summary_to_thread)
.map(|summary| summary_to_thread(summary, &config.cwd))
.map_err(|err| {
format!(
"failed to load rollout `{}` for thread {thread_id}: {err}",
@@ -9612,10 +9607,13 @@ async fn load_thread_summary_for_rollout(
if let Some(persisted_metadata) = persisted_metadata {
merge_mutable_thread_metadata(
&mut thread,
summary_to_thread(summary_from_thread_metadata(persisted_metadata)),
summary_to_thread(
summary_from_thread_metadata(persisted_metadata),
&config.cwd,
),
);
} else if let Some(summary) = read_summary_from_state_db_by_thread_id(config, thread_id).await {
merge_mutable_thread_metadata(&mut thread, summary_to_thread(summary));
merge_mutable_thread_metadata(&mut thread, summary_to_thread(summary, &config.cwd));
}
let title = if let Some(metadata) = persisted_metadata {
non_empty_title(metadata)
@@ -9735,7 +9733,10 @@ fn build_thread_from_snapshot(
}
}
pub(crate) fn summary_to_thread(summary: ConversationSummary) -> Thread {
pub(crate) fn summary_to_thread(
summary: ConversationSummary,
fallback_cwd: &AbsolutePathBuf,
) -> Thread {
let ConversationSummary {
conversation_id,
path,
@@ -9756,6 +9757,15 @@ pub(crate) fn summary_to_thread(summary: ConversationSummary) -> Thread {
branch: info.branch,
origin_url: info.origin_url,
});
let cwd =
AbsolutePathBuf::relative_to_current_dir(path_utils::normalize_for_native_workdir(cwd))
.unwrap_or_else(|err| {
warn!(
path = %path.display(),
"failed to normalize thread cwd while summarizing thread: {err}"
);
fallback_cwd.clone()
});
Thread {
id: conversation_id.to_string(),
@@ -9789,6 +9799,8 @@ mod tests {
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
@@ -9923,7 +9935,7 @@ mod tests {
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
ephemeral: false,
reasoning_effort: None,
personality: None,
@@ -10283,7 +10295,8 @@ mod tests {
fs::write(&path, format!("{}\n", serde_json::to_string(&line)?))?;
let summary = read_summary_from_rollout(path.as_path(), "fallback").await?;
let thread = summary_to_thread(summary);
let fallback_cwd = AbsolutePathBuf::from_absolute_path("/")?;
let thread = summary_to_thread(summary, &fallback_cwd);
assert_eq!(thread.agent_nickname, Some("atlas".to_string()));
assert_eq!(thread.agent_role, Some("explorer".to_string()));
@@ -10417,7 +10430,8 @@ mod tests {
/*git_origin_url*/ None,
);
let thread = summary_to_thread(summary);
let fallback_cwd = AbsolutePathBuf::from_absolute_path("/")?;
let thread = summary_to_thread(summary, &fallback_cwd);
assert_eq!(thread.agent_nickname, Some("atlas".to_string()));
assert_eq!(thread.agent_role, Some("explorer".to_string()));

View File

@@ -14,7 +14,6 @@ use codex_core::file_watcher::FileWatcherSubscriber;
use codex_core::file_watcher::Receiver;
use codex_core::file_watcher::WatchPath;
use codex_core::file_watcher::WatchRegistration;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::hash_map::Entry;
@@ -128,7 +127,7 @@ impl FsWatchManager {
};
let outgoing = self.outgoing.clone();
let (subscriber, rx) = self.file_watcher.add_subscriber();
let watch_root = params.path.to_path_buf().clone();
let watch_root = params.path.clone();
let registration = subscriber.register_paths(vec![WatchPath {
path: params.path.to_path_buf(),
recursive: false,
@@ -166,7 +165,7 @@ impl FsWatchManager {
let mut changed_paths = event
.paths
.into_iter()
.map(|path| AbsolutePathBuf::resolve_path_against_base(&path, &watch_root))
.map(|path| watch_root.join(path))
.collect::<Vec<_>>();
changed_paths.sort_by(|left, right| left.as_path().cmp(right.as_path()));
if !changed_paths.is_empty() {

View File

@@ -8,6 +8,7 @@ use codex_core::CodexThread;
use codex_core::ThreadConfigSnapshot;
use codex_protocol::ThreadId;
use codex_protocol::protocol::EventMsg;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
@@ -28,7 +29,7 @@ pub(crate) struct PendingThreadResumeRequest {
pub(crate) request_id: ConnectionRequestId,
pub(crate) rollout_path: PathBuf,
pub(crate) config_snapshot: ThreadConfigSnapshot,
pub(crate) instruction_sources: Vec<PathBuf>,
pub(crate) instruction_sources: Vec<AbsolutePathBuf>,
pub(crate) thread_summary: codex_app_server_protocol::Thread,
}

View File

@@ -10,8 +10,6 @@ use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::ThreadStatusChangedNotification;
use codex_protocol::ThreadId;
use std::collections::HashMap;
#[cfg(test)]
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(test)]
@@ -455,6 +453,8 @@ fn loaded_thread_status(runtime: &RuntimeFacts) -> ThreadStatus {
#[cfg(test)]
mod tests {
use super::*;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use tokio::time::Duration;
use tokio::time::timeout;
@@ -895,7 +895,7 @@ mod tests {
updated_at: 0,
status: ThreadStatus::NotLoaded,
path: None,
cwd: PathBuf::from("/tmp"),
cwd: test_path_buf("/tmp").abs(),
cli_version: "test".to_string(),
agent_nickname: None,
agent_role: None,

View File

@@ -402,7 +402,6 @@ mod tests {
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
use tokio::time::Duration;
use tokio::time::timeout;
@@ -772,7 +771,7 @@ mod tests {
reason: Some("Need extra read access".to_string()),
network_approval_context: None,
command: Some("cat file".to_string()),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(absolute_path("/tmp")),
command_actions: None,
additional_permissions: Some(
codex_app_server_protocol::AdditionalPermissionProfile {
@@ -834,7 +833,7 @@ mod tests {
reason: Some("Need extra read access".to_string()),
network_approval_context: None,
command: Some("cat file".to_string()),
cwd: Some(PathBuf::from("/tmp")),
cwd: Some(absolute_path("/tmp")),
command_actions: None,
additional_permissions: Some(
codex_app_server_protocol::AdditionalPermissionProfile {