unified-exec: preserve PathUri through exec-server (#28681)

## Why

It should be possible for app-server to handle "foreign" OS paths in
unified_exec working directories, allowing e.g. a Linux app-server to
run processes on e.g. a Windows exec-server.

## What

Convert the core unified_exec cwd values to use `PathUri`.

Adds fallible path conversion in several places to try to minimize the
scope of this change. The only time this change suppresses errors from
converting `PathUri` to an `AbsolutePathBuf` is when the turn is
configured with no sandboxing at all to allow us to make progress
testing without sandboxing.

Future changes to apply_patch and sandboxing will clean up these error
paths.

A tool's cwd is resolved from joining a model-provided workdir to the
environment's cwd. When using `AbsolutePathBuf::join()`, an
absolute-path workdir would overwrite the environment's cwd and we would
resolve permissions/sandboxing against the model-provided path. This
change extends `PathUri::join()` to also treat an absolute rhs as an
override of the base/lhs.

This also removes some coverage from the remove_env_windows tests until
a follow-up converts foreign paths in command exec events correctly.

## Breaking Changes

When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
`~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
`PathUri` joining, so after offline discussion this PR no longer
implements it.

A quick check of some power users' rollouts suggests that models don't
actually generate home-prefixed absolute working directories for their
spawns, so this shouldn't have any real blast radius.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-17 12:36:16 -07:00
committed by GitHub
Unverified
parent cca39d51ba
commit 5867b529ae
33 changed files with 634 additions and 206 deletions
+8 -3
View File
@@ -230,7 +230,7 @@ pub async fn verify_apply_patch_args(
MaybeApplyPatchVerified::Body(ApplyPatchAction {
changes,
patch,
cwd: effective_cwd,
cwd: effective_cwd.into(),
})
}
@@ -811,7 +811,9 @@ PATCH"#,
},
)]),
patch: argv[1].clone(),
cwd: AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
cwd: AbsolutePathBuf::from_absolute_path(session_dir.path())
.unwrap()
.into(),
})
);
}
@@ -851,7 +853,10 @@ PATCH"#,
other => panic!("expected verified body, got {other:?}"),
};
assert_eq!(action.cwd.as_path(), worktree_dir.as_path());
assert_eq!(
action.cwd.to_abs_path().unwrap().as_path(),
worktree_dir.as_path()
);
let source_path = worktree_dir.join(source_name);
let change = action