From 0f9606e9e313c777811d44ac15565f798c1a4c28 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 28 Feb 2026 15:15:40 -0500 Subject: [PATCH] =?UTF-8?q?Write=20final.patch=20(base=5Fsha=20=E2=86=92?= =?UTF-8?q?=20HEAD)=20to=20logs=5Froot=20after=20pipeline=20completes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-stage diff.patch files only capture incremental changes between checkpoints. This adds a comprehensive final.patch covering all changes from the run's base SHA to the final HEAD, written to logs_root for all execution environments. Especially important for Daytona where the sandbox is destroyed after the run. Co-Authored-By: Claude Opus 4.6 --- crates/arc-attractor/src/engine.rs | 15 +++++++++++++++ crates/arc-attractor/tests/daytona_integration.rs | 6 ++++++ crates/arc-attractor/tests/integration.rs | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/crates/arc-attractor/src/engine.rs b/crates/arc-attractor/src/engine.rs index f49c9e8ea..ed6933467 100644 --- a/crates/arc-attractor/src/engine.rs +++ b/crates/arc-attractor/src/engine.rs @@ -1211,6 +1211,21 @@ impl PipelineEngine { final_git_commit_sha: last_git_sha.clone(), }); + // Write final.patch: comprehensive diff from base_sha to HEAD + if let (Some(ref mode), Some(ref base)) = (&config.git_checkpoint, &config.base_sha) { + let patch = match mode { + GitCheckpointMode::Host(work_dir) => { + git_diff_host(work_dir.clone(), base.clone()).await + } + GitCheckpointMode::Remote => { + git_diff_remote(&*self.services.execution_env, base).await + } + }; + if let Some(patch) = patch { + let _ = std::fs::write(config.logs_root.join("final.patch"), patch); + } + } + // Return last outcome, or success if no outcomes recorded let last_outcome = node_outcomes .get(completed_nodes.last().unwrap_or(&String::new())) diff --git a/crates/arc-attractor/tests/daytona_integration.rs b/crates/arc-attractor/tests/daytona_integration.rs index 8f8527716..985148d4d 100644 --- a/crates/arc-attractor/tests/daytona_integration.rs +++ b/crates/arc-attractor/tests/daytona_integration.rs @@ -474,6 +474,12 @@ async fn daytona_git_checkpoint_remote_emits_events() { "checkpoint should have git_commit_sha" ); + // Assert final.patch exists and contains changes from the run + let final_patch = dir.path().join("final.patch"); + assert!(final_patch.exists(), "final.patch should exist in logs_root"); + let patch_content = std::fs::read_to_string(&final_patch).unwrap(); + assert!(!patch_content.is_empty(), "final.patch should not be empty"); + env.cleanup().await.unwrap(); } diff --git a/crates/arc-attractor/tests/integration.rs b/crates/arc-attractor/tests/integration.rs index 3058e4b61..cc59fbf91 100644 --- a/crates/arc-attractor/tests/integration.rs +++ b/crates/arc-attractor/tests/integration.rs @@ -8395,6 +8395,12 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { "checkpoint should have git_commit_sha" ); + // 9. Assert final.patch exists and contains the changes + let final_patch = logs_dir.path().join("final.patch"); + assert!(final_patch.exists(), "final.patch should exist in logs_root"); + let patch_content = std::fs::read_to_string(&final_patch).unwrap(); + assert!(patch_content.contains("hello.txt"), "final.patch should contain hello.txt changes"); + // Cleanup worktree let _ = std::process::Command::new("git") .args(["worktree", "remove", "--force"])