unified-exec: retain PathUri in command events (#28780)

## Why

App-server must report command events containing foreign-platform paths
without changing existing client or rollout path-string formats.

## What changed

- retain `PathUri` through exec command begin/end events
- convert cwd values to `LegacyAppPathString` at the app-server
compatibility boundary
- drop command actions with foreign paths and log them
- serialize rollout-trace cwd values using their inferred native path
representation
- restore Wine coverage for retained Windows cwd values and successful
completion
This commit is contained in:
Adam Perry @ OpenAI
2026-06-17 22:00:04 -07:00
committed by GitHub
Unverified
parent 285eff6c3e
commit 3931bc2bde
56 changed files with 566 additions and 125 deletions
+4 -4
View File
@@ -188,7 +188,7 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
started_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.clone().into(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -262,7 +262,7 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.clone().into(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -287,7 +287,7 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command.clone(),
cwd: cwd.clone(),
cwd: cwd.clone().into(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::UserShell,
interaction_input: None,
@@ -332,7 +332,7 @@ pub(crate) async fn execute_user_shell_command(
turn_id: turn_context.sub_id.clone(),
completed_at_ms: now_unix_timestamp_ms(),
command: display_command,
cwd,
cwd: cwd.into(),
parsed_cmd,
source: ExecCommandSource::UserShell,
interaction_input: None,
+6 -10
View File
@@ -96,7 +96,7 @@ fn tracker_update_for_known_delta<'a>(
pub(crate) async fn emit_exec_command_begin(
ctx: ToolEventCtx<'_>,
command: &[String],
cwd: &AbsolutePathBuf,
cwd: &PathUri,
parsed_cmd: &[ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<String>,
@@ -123,7 +123,7 @@ pub(crate) async fn emit_exec_command_begin(
pub(crate) enum ToolEmitter {
Shell {
command: Vec<String>,
cwd: AbsolutePathBuf,
cwd: PathUri,
source: ExecCommandSource,
parsed_cmd: Vec<ParsedCommand>,
},
@@ -146,7 +146,7 @@ impl ToolEmitter {
let parsed_cmd = parse_command(&command);
Self::Shell {
command,
cwd,
cwd: PathUri::from_abs_path(&cwd),
source,
parsed_cmd,
}
@@ -321,15 +321,11 @@ impl ToolEmitter {
},
stage,
) => {
// TODO(anp): Migrate exec command protocol events to PathUri.
let Ok(cwd) = cwd.to_abs_path() else {
return;
};
emit_exec_stage(
ctx,
ExecCommandInput::new(
command,
&cwd,
cwd,
parsed_cmd,
*source,
/*interaction_input*/ None,
@@ -437,7 +433,7 @@ impl ToolEmitter {
struct ExecCommandInput<'a> {
command: &'a [String],
cwd: &'a AbsolutePathBuf,
cwd: &'a PathUri,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -447,7 +443,7 @@ struct ExecCommandInput<'a> {
impl<'a> ExecCommandInput<'a> {
fn new(
command: &'a [String],
cwd: &'a AbsolutePathBuf,
cwd: &'a PathUri,
parsed_cmd: &'a [ParsedCommand],
source: ExecCommandSource,
interaction_input: Option<&'a str>,
@@ -20,6 +20,7 @@ use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandStatus;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::protocol::TurnEnvironmentSelections;
@@ -127,10 +128,6 @@ async fn windows_exec_server_runs_with_native_shell_and_cwd() -> Result<()> {
})
.await?;
// TODO(anp): Re-enable these event assertions once exec command events retain a
// PathUri cwd. Today the host-native event conversion drops begin/end events for a
// foreign cwd.
/*
let mut begin = None;
let mut end = None;
let mut turn_complete = false;
@@ -161,13 +158,9 @@ async fn windows_exec_server_runs_with_native_shell_and_cwd() -> Result<()> {
assert_eq!(&begin.command[1..], ["-NoProfile", "-Command", COMMAND]);
let end = end.context("exec_command should emit an end event")?;
let expected_cwd = PathUri::parse("file:///C:/windows")?;
assert_eq!((&begin.cwd, &end.cwd), (&expected_cwd, &expected_cwd));
assert_eq!((end.exit_code, end.status), (0, ExecCommandStatus::Completed));
*/
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
let request = response_mock
.last_request()
+5 -5
View File
@@ -437,7 +437,7 @@ async fn unified_exec_emits_exec_command_begin_event() -> Result<()> {
assert_command(&begin_event.command, "-lc", "/bin/echo hello unified exec");
assert_eq!(begin_event.cwd.as_path(), cwd.as_path());
assert_eq!(begin_event.cwd, PathUri::from_path(&cwd)?);
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
@@ -507,8 +507,8 @@ async fn unified_exec_resolves_relative_workdir() -> Result<()> {
.await;
assert_eq!(
begin_event.cwd.as_path(),
workdir.as_path(),
begin_event.cwd,
PathUri::from_path(&workdir)?,
"exec_command cwd should resolve relative workdir against turn cwd",
);
@@ -569,8 +569,8 @@ async fn unified_exec_respects_workdir_override() -> Result<()> {
.await;
assert_eq!(
begin_event.cwd.as_path(),
workdir.as_path(),
begin_event.cwd,
PathUri::from_path(&workdir)?,
"exec_command cwd should reflect the requested workdir override"
);