app-server + external exec-server: `apply_patch` delete never reaches the environment's `fs/remove` — deletes hit the host-local filesystem and fail
Summary
Running codex app-server (codex-cli 0.144.3) with an external sandbox
exec-server environment (sandboxPolicy: {type: "externalSandbox"},environments: [{environmentId: …}]), apply_patch Add/Update hunks are
correctly routed through the environment's fs/writeFile, but Delete
hunks never emit any fs/* request. The delete is attempted against the
app-server host's local filesystem instead, where the environment-internal
path does not exist, and fails with Failed to delete file <path>.
Possibly related: #18534 reports the same symptom (Add/Update succeed, Delete
fails with Failed to delete file) on plain CLI, without the wire-level
analysis below.
Environment
- codex-cli 0.144.3 (
@openai/codex-linux-x64, x86_64-unknown-linux-musl) - app-server via stdio, code mode active (
session/executefreeform
apply_patch tool through codex-code-mode-host)
- External exec-server environment implementing the documented protocol
(fs/readFile, fs/writeFile, fs/remove, fs/getMetadata, process/*)
- Embedder: OpenClaw gateway; environment maps
/workspaceinto a Docker
container. The app-server host has no /workspace.
Reproduction
- Register an external exec-server environment whose filesystem root (e.g.
/workspace) exists only inside the environment, not on the app-server host.
- Prompt a turn to delete an existing file via
apply_patch
(*** Delete File: /workspace/tmp/x/DELETE_ME.md).
- Observe (syscall trace of all
write/writev/sendmsgon the app-server
process tree): zero fs/* requests are sent for the delete; the tool call
fails with exit code 1 and output Failed to delete file /workspace/tmp/x/DELETE_ME.md.
- Control: an Add or Update hunk on the same path in the same session emits
fs/writeFile and succeeds inside the environment.
Expected
Delete hunks should use the same ExecutorFileSystem bridging as writes —codex-rs/apply-patch/src/lib.rs (rust-v0.144.3) callsensure_not_directory → fs.get_metadata(...) and fs.remove(...,, so the
RemoveOptions { recursive: false, force: false }, sandbox)
implementation clearly intends to route through the abstraction; in the
app-server + external-environment constellation those calls never reach the
environment.
Impact
Every apply_patch delete in an externally sandboxed embedding fails; models
fall back to shell rm. Confirmed on two independent turns with full-tree
syscall traces; the embedder's fs/remove handler was verified working by
driving it directly with the exact recursive:false, force:false options.