From b718a6d3e1beaf90d54b1763e3b26aa3182d8e41 Mon Sep 17 00:00:00 2001 From: Vivien MALEZE Date: Fri, 27 Feb 2026 15:22:21 +0100 Subject: [PATCH 1/4] fix(task): prevent broken pipe panic and race condition in remote git task cache Remote git task fetching could panic on EPIPE when stdout/stderr was piped, and concurrent fetches of the same repo could corrupt the cache. Use writeln! instead of println!/eprintln! to gracefully handle broken pipes, add file locking per cache key to serialize concurrent access, and clone to a temp directory before renaming into place to avoid partial cache state. Made-with: Cursor --- src/cmd.rs | 18 +++--- .../task_file_providers/remote_task_git.rs | 57 +++++++++++++------ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/cmd.rs b/src/cmd.rs index 57b809b068..42a5c2ffe9 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -598,10 +598,13 @@ impl<'a> CmdLineRunner<'a> { if !line.trim().is_empty() { pr.set_message(line) } - } else if console::colors_enabled() { - println!("{line}\x1b[0m"); } else { - println!("{line}"); + let mut stdout = std::io::stdout().lock(); + let _ = if console::colors_enabled() { + writeln!(stdout, "{line}\x1b[0m") + } else { + writeln!(stdout, "{line}") + }; } } @@ -621,11 +624,12 @@ impl<'a> CmdLineRunner<'a> { } } None => { - if console::colors_enabled_stderr() { - eprintln!("{line}\x1b[0m"); + let mut stderr = std::io::stderr().lock(); + let _ = if console::colors_enabled_stderr() { + writeln!(stderr, "{line}\x1b[0m") } else { - eprintln!("{line}"); - } + writeln!(stderr, "{line}") + }; } } } diff --git a/src/task/task_file_providers/remote_task_git.rs b/src/task/task_file_providers/remote_task_git.rs index 9e8655b40b..d612e93e73 100644 --- a/src/task/task_file_providers/remote_task_git.rs +++ b/src/task/task_file_providers/remote_task_git.rs @@ -7,8 +7,10 @@ use async_trait::async_trait; use crate::{ dirs, env, + file::display_path, git::{self, CloneOptions}, hash, + lock_file::LockFile, }; use super::TaskFileProvider; @@ -123,25 +125,31 @@ impl TaskFileProvider for RemoteTaskGit { debug!("Repo structure: {:?}", repo_structure); - match self.is_cached { - true => { - trace!("Cache mode enabled"); - - if full_path.exists() { - debug!("Using cached file: {:?}", full_path); - return Ok(full_path); - } + let _lock = LockFile::new(&destination) + .with_callback(|l| { + debug!( + "waiting for lock on remote git task cache: {}", + display_path(l) + ); + }) + .lock()?; + + if self.is_cached { + trace!("Cache mode enabled"); + if full_path.exists() { + debug!("Using cached file: {:?}", full_path); + return Ok(full_path); } - false => { - trace!("Cache mode disabled"); + } else { + trace!("Cache mode disabled"); + } - if full_path.exists() { - crate::file::remove_all(&destination)?; - } - } + let tmp_destination = self.storage_path.join(format!("{}.clone-tmp", &cache_key)); + if tmp_destination.exists() { + crate::file::remove_all(&tmp_destination)?; } - let git_repo = git::Git::new(destination); + let git_repo = git::Git::new(&tmp_destination); let mut clone_options = CloneOptions::default(); @@ -150,7 +158,24 @@ impl TaskFileProvider for RemoteTaskGit { clone_options = clone_options.branch(branch); } - git_repo.clone(repo_structure.url_without_path.as_str(), clone_options)?; + match git_repo.clone(repo_structure.url_without_path.as_str(), clone_options) { + Ok(()) => { + if destination.exists() { + crate::file::remove_all(&destination)?; + } + std::fs::rename(&tmp_destination, &destination).map_err(|e| { + let _ = crate::file::remove_all(&tmp_destination); + eyre::eyre!( + "failed to move cloned repo into cache at {}: {e}", + display_path(&destination) + ) + })?; + } + Err(e) => { + let _ = crate::file::remove_all(&tmp_destination); + return Err(e); + } + } Ok(full_path) } From 3f569d5fe7d7457c47c95d9b475ed6e240658106 Mon Sep 17 00:00:00 2001 From: Vivien MALEZE Date: Fri, 27 Feb 2026 15:32:34 +0100 Subject: [PATCH 2/4] fix(task): clean up tmp directory on all error paths after clone Made-with: Cursor --- src/task/task_file_providers/remote_task_git.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/task/task_file_providers/remote_task_git.rs b/src/task/task_file_providers/remote_task_git.rs index d612e93e73..082a2ecdb0 100644 --- a/src/task/task_file_providers/remote_task_git.rs +++ b/src/task/task_file_providers/remote_task_git.rs @@ -161,15 +161,18 @@ impl TaskFileProvider for RemoteTaskGit { match git_repo.clone(repo_structure.url_without_path.as_str(), clone_options) { Ok(()) => { if destination.exists() { - crate::file::remove_all(&destination)?; + if let Err(e) = crate::file::remove_all(&destination) { + let _ = crate::file::remove_all(&tmp_destination); + return Err(e); + } } - std::fs::rename(&tmp_destination, &destination).map_err(|e| { + if let Err(e) = std::fs::rename(&tmp_destination, &destination) { let _ = crate::file::remove_all(&tmp_destination); - eyre::eyre!( + return Err(eyre::eyre!( "failed to move cloned repo into cache at {}: {e}", display_path(&destination) - ) - })?; + )); + } } Err(e) => { let _ = crate::file::remove_all(&tmp_destination); From 9bc731c95c7b9f67a3af2dddff6db08d817e09e4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:37:51 +0000 Subject: [PATCH 3/4] [autofix.ci] apply automated fixes --- src/task/task_file_providers/remote_task_git.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/task/task_file_providers/remote_task_git.rs b/src/task/task_file_providers/remote_task_git.rs index 082a2ecdb0..f22cc8b869 100644 --- a/src/task/task_file_providers/remote_task_git.rs +++ b/src/task/task_file_providers/remote_task_git.rs @@ -160,12 +160,11 @@ impl TaskFileProvider for RemoteTaskGit { match git_repo.clone(repo_structure.url_without_path.as_str(), clone_options) { Ok(()) => { - if destination.exists() { - if let Err(e) = crate::file::remove_all(&destination) { + if destination.exists() + && let Err(e) = crate::file::remove_all(&destination) { let _ = crate::file::remove_all(&tmp_destination); return Err(e); } - } if let Err(e) = std::fs::rename(&tmp_destination, &destination) { let _ = crate::file::remove_all(&tmp_destination); return Err(eyre::eyre!( From e29886010cec4014bfacb07b821770a5d0d37185 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:41:46 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes (attempt 2/3) --- src/task/task_file_providers/remote_task_git.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/task/task_file_providers/remote_task_git.rs b/src/task/task_file_providers/remote_task_git.rs index f22cc8b869..15cedff7e8 100644 --- a/src/task/task_file_providers/remote_task_git.rs +++ b/src/task/task_file_providers/remote_task_git.rs @@ -161,10 +161,11 @@ impl TaskFileProvider for RemoteTaskGit { match git_repo.clone(repo_structure.url_without_path.as_str(), clone_options) { Ok(()) => { if destination.exists() - && let Err(e) = crate::file::remove_all(&destination) { - let _ = crate::file::remove_all(&tmp_destination); - return Err(e); - } + && let Err(e) = crate::file::remove_all(&destination) + { + let _ = crate::file::remove_all(&tmp_destination); + return Err(e); + } if let Err(e) = std::fs::rename(&tmp_destination, &destination) { let _ = crate::file::remove_all(&tmp_destination); return Err(eyre::eyre!(