Make turn diff tracking operation backed (#21180)

## Summary
- replace filesystem-based turn diff tracking with an operation-backed
accumulator
- preserve enough verified apply_patch state to render move-overwrite
cases correctly
- keep the turn/diff/updated contract intact while removing remote-only
turn-diff test skips

This takes the assumption that no 3P services rely on the output format
of `apply_patch`

## Why
For the CCA file system isolation push

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-05-07 11:33:47 +02:00
committed by GitHub
co-authored by Codex
parent b2268999fe
commit f7e8ff8e50
15 changed files with 1050 additions and 898 deletions
+149 -75
View File
@@ -12,6 +12,7 @@ use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering;
use std::time::Duration;
use codex_exec_server::CreateDirectoryOptions;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
@@ -377,10 +378,6 @@ async fn apply_patch_cli_move_without_content_change_has_no_turn_diff(
model_output: ApplyPatchModelOutput,
) -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness().await?;
let test = harness.test();
@@ -1050,10 +1047,6 @@ async fn apply_patch_custom_tool_streaming_emits_updated_changes() -> Result<()>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_shell_command_heredoc_with_cd_emits_turn_diff() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness_with(|builder| builder.with_model("gpt-5.4")).await?;
let test = harness.test();
@@ -1113,13 +1106,88 @@ async fn apply_patch_shell_command_heredoc_with_cd_emits_turn_diff() -> Result<(
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_turn_diff_paths_stay_repo_relative_when_session_cwd_is_nested() -> Result<()> {
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness_with(|builder| {
builder
.with_model("gpt-5.4")
.with_config(|config| {
config.cwd = config.cwd.join("subdir");
})
.with_workspace_setup(|cwd, fs| async move {
fs.create_directory(
&cwd,
CreateDirectoryOptions { recursive: true },
/*sandbox*/ None,
)
.await?;
let repo_root = cwd.parent().expect("nested cwd should have parent");
fs.write_file(
&repo_root.join(".git"),
b"gitdir: /tmp/fake-worktree\n".to_vec(),
/*sandbox*/ None,
)
.await?;
fs.write_file(
&repo_root.join("repo.txt"),
b"before\n".to_vec(),
/*sandbox*/ None,
)
.await?;
Ok(())
})
})
.await?;
let test = harness.test();
let codex = test.codex.clone();
let repo_root = harness
.test()
.config
.cwd
.parent()
.expect("nested cwd should have parent");
let call_id = "apply-nested-cwd-repo-relative";
let patch = "*** Begin Patch\n*** Update File: ../repo.txt\n@@\n-before\n+after\n*** End Patch";
mount_apply_patch(
&harness,
call_id,
patch,
"updated repo-relative path",
ApplyPatchModelOutput::Function,
)
.await;
submit_without_wait(&harness, "update file outside nested cwd but inside repo").await?;
let mut last_diff: Option<String> = None;
wait_for_event(&codex, |event| match event {
EventMsg::TurnDiff(ev) => {
last_diff = Some(ev.unified_diff.clone());
false
}
EventMsg::TurnComplete(_) => true,
_ => false,
})
.await;
let diff = last_diff.expect("expected TurnDiff event after update");
assert!(
diff.contains("diff --git a/repo.txt b/repo.txt"),
"diff should stay repo-relative: {diff:?}"
);
assert!(
!diff.contains(repo_root.as_path().to_string_lossy().as_ref()),
"diff should not leak absolute repo paths: {diff:?}"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_shell_command_failure_propagates_error_and_skips_diff() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness_with(|builder| builder.with_model("gpt-5.4")).await?;
let test = harness.test();
@@ -1265,10 +1333,6 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff(
model_output: ApplyPatchModelOutput,
) -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness().await?;
let test = harness.test();
@@ -1300,64 +1364,9 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff(
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[test_case(ApplyPatchModelOutput::Freeform)]
#[test_case(ApplyPatchModelOutput::Function)]
#[test_case(ApplyPatchModelOutput::Shell)]
#[test_case(ApplyPatchModelOutput::ShellViaHeredoc)]
#[test_case(ApplyPatchModelOutput::ShellCommandViaHeredoc)]
async fn apply_patch_turn_diff_for_rename_with_content_change(
model_output: ApplyPatchModelOutput,
) -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness().await?;
let test = harness.test();
let codex = test.codex.clone();
// Seed original file
harness.write_file("old.txt", "old\n").await?;
// Patch: update + move
let call_id = "apply-rename-change";
let patch = "*** Begin Patch\n*** Update File: old.txt\n*** Move to: new.txt\n@@\n-old\n+new\n*** End Patch";
mount_apply_patch(&harness, call_id, patch, "ok", model_output).await;
submit_without_wait(&harness, "rename with change").await?;
let mut last_diff: Option<String> = None;
wait_for_event(&codex, |event| match event {
EventMsg::TurnDiff(ev) => {
last_diff = Some(ev.unified_diff.clone());
false
}
EventMsg::TurnComplete(_) => true,
_ => false,
})
.await;
let diff = last_diff.expect("expected TurnDiff event after rename");
// Basic checks: shows old -> new, and the content delta
assert!(diff.contains("old.txt"), "diff missing old path: {diff:?}");
assert!(diff.contains("new.txt"), "diff missing new path: {diff:?}");
assert!(diff.contains("--- a/"), "missing old header");
assert!(diff.contains("+++ b/"), "missing new header");
assert!(diff.contains("-old\n"), "missing removal line");
assert!(diff.contains("+new\n"), "missing addition line");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_aggregates_diff_across_multiple_tool_calls() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness().await?;
let test = harness.test();
@@ -1408,10 +1417,6 @@ async fn apply_patch_aggregates_diff_across_multiple_tool_calls() -> Result<()>
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_aggregates_diff_preserves_success_after_failure() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_remote!(
Ok(()),
"TurnDiffTracker currently reads the test-runner filesystem, not the remote executor filesystem",
);
let harness = apply_patch_harness().await?;
let test = harness.test();
@@ -1482,6 +1487,75 @@ async fn apply_patch_aggregates_diff_preserves_success_after_failure() -> Result
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn apply_patch_clears_aggregated_diff_after_inexact_delta() -> Result<()> {
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness_with(|builder| {
builder.with_workspace_setup(|cwd, fs| async move {
fs.write_file(
&cwd.join("binary.dat"),
vec![0xff, 0xfe, 0xfd],
/*sandbox*/ None,
)
.await?;
Ok(())
})
})
.await?;
let test = harness.test();
let codex = test.codex.clone();
let call_success = "agg-success";
let call_inexact = "agg-inexact";
let patch_success = "*** Begin Patch\n*** Add File: partial/success.txt\n+ok\n*** End Patch";
let patch_inexact = "*** Begin Patch\n*** Add File: binary.dat\n+text\n*** End Patch";
let responses = vec![
sse(vec![
ev_response_created("resp-1"),
ev_apply_patch_function_call(call_success, patch_success),
ev_completed("resp-1"),
]),
sse(vec![
ev_response_created("resp-2"),
ev_apply_patch_function_call(call_inexact, patch_inexact),
ev_completed("resp-2"),
]),
sse(vec![
ev_assistant_message("msg-1", "done"),
ev_completed("resp-3"),
]),
];
mount_sse_sequence(harness.server(), responses).await;
submit_without_wait(&harness, "apply patch twice with inexact delta").await?;
let mut last_diff: Option<String> = None;
wait_for_event_with_timeout(
&codex,
|event| match event {
EventMsg::TurnDiff(ev) => {
last_diff = Some(ev.unified_diff.clone());
false
}
EventMsg::TurnComplete(_) => true,
_ => false,
},
Duration::from_secs(30),
)
.await;
assert_eq!(
last_diff.as_deref(),
Some(""),
"inexact delta should clear the aggregate diff"
);
assert_eq!(harness.read_file_text("partial/success.txt").await?, "ok\n");
assert_eq!(harness.read_file_text("binary.dat").await?, "text\n");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[test_case(ApplyPatchModelOutput::Freeform)]
#[test_case(ApplyPatchModelOutput::Function)]