diff --git a/lib/crates/arc-workflows/src/cli/run.rs b/lib/crates/arc-workflows/src/cli/run.rs index 5b2b3b9b9..e4d0a61fe 100644 --- a/lib/crates/arc-workflows/src/cli/run.rs +++ b/lib/crates/arc-workflows/src/cli/run.rs @@ -737,6 +737,7 @@ pub async fn run_command( config, github_app.clone(), Some(run_id.clone()), + detected_base_branch.clone(), ); let emitter_cb = Arc::clone(&emitter); env.set_event_callback(Arc::new(move |event| { @@ -1923,6 +1924,7 @@ async fn run_preflight( config, github_app, None, + None, ); Ok(Arc::new(env) as Arc) } diff --git a/lib/crates/arc-workflows/src/daytona_sandbox.rs b/lib/crates/arc-workflows/src/daytona_sandbox.rs index 3d9443b14..97f793c44 100644 --- a/lib/crates/arc-workflows/src/daytona_sandbox.rs +++ b/lib/crates/arc-workflows/src/daytona_sandbox.rs @@ -160,6 +160,10 @@ pub struct DaytonaSandbox { /// HTTPS origin URL stored after clone so we can refresh push credentials later. origin_url: tokio::sync::OnceCell, run_id: Option, + /// Explicit branch to clone. When set, overrides the branch detected by + /// `detect_repo_info` — avoids cloning a local-only worktree branch + /// (e.g. `arc/run/...`) that was never pushed to origin. + clone_branch: Option, } impl DaytonaSandbox { @@ -169,6 +173,7 @@ impl DaytonaSandbox { config: DaytonaConfig, github_app: Option, run_id: Option, + clone_branch: Option, ) -> Self { Self { config, @@ -179,6 +184,7 @@ impl DaytonaSandbox { event_callback: None, origin_url: tokio::sync::OnceCell::new(), run_id, + clone_branch, } } @@ -197,6 +203,7 @@ impl DaytonaSandbox { event_callback: None, origin_url: tokio::sync::OnceCell::new(), run_id: None, + clone_branch: None, } } @@ -562,7 +569,10 @@ impl Sandbox for DaytonaSandbox { // Clone the repo into the sandbox match detect_repo_info(&cwd) { - Ok((detected_url, branch)) => { + Ok((detected_url, detected_branch)) => { + // Use explicit clone_branch if provided (avoids cloning a local-only + // worktree branch like arc/run/... that hasn't been pushed). + let branch = self.clone_branch.clone().or(detected_branch); // Daytona clones over HTTPS with token auth, so rewrite SSH URLs. let url = ssh_url_to_https(&detected_url); self.emit(SandboxEvent::GitCloneStarted { @@ -1369,4 +1379,33 @@ mod tests { "unexpected error: {msg}" ); } + + #[test] + fn detect_repo_info_returns_worktree_branch() { + let dir = tempfile::tempdir().unwrap(); + let repo = git2::Repository::init(dir.path()).unwrap(); + + // Create an initial commit so HEAD exists + let sig = git2::Signature::now("Test", "test@test.com").unwrap(); + let tree_id = repo.index().unwrap().write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + let commit = repo + .commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]) + .unwrap(); + + repo.remote("origin", "https://github.com/org/repo.git") + .unwrap(); + + // Create and check out an arc/run/... branch (simulating worktree setup) + let commit_obj = repo.find_commit(commit).unwrap(); + repo.branch("arc/run/ABC", &commit_obj, false).unwrap(); + repo.set_head("refs/heads/arc/run/ABC").unwrap(); + + let (_, branch) = detect_repo_info(dir.path()).unwrap(); + // Documents the current behavior: detect_repo_info returns whatever HEAD points to + assert_eq!(branch, Some("arc/run/ABC".into())); + } + + // The `clone_branch` field on `DaytonaSandbox::new()` is verified at compile time — + // all call sites (run.rs, integration tests) must pass the new parameter. } diff --git a/lib/crates/arc-workflows/tests/daytona_integration.rs b/lib/crates/arc-workflows/tests/daytona_integration.rs index 2be153e9e..ee3a55b23 100644 --- a/lib/crates/arc-workflows/tests/daytona_integration.rs +++ b/lib/crates/arc-workflows/tests/daytona_integration.rs @@ -37,7 +37,7 @@ async fn create_env_with_github_app( let client = daytona_sdk::Client::new() .await .expect("Failed to create Daytona client — is DAYTONA_API_KEY set?"); - DaytonaSandbox::new(client, DaytonaConfig::default(), github_app, None) + DaytonaSandbox::new(client, DaytonaConfig::default(), github_app, None, None) } fn load_github_app_credentials() -> arc_github::GitHubAppCredentials { @@ -253,7 +253,7 @@ async fn daytona_snapshot_sandbox() { }; let creds = load_github_app_credentials(); - let env = DaytonaSandbox::new(client, config, Some(creds), None); + let env = DaytonaSandbox::new(client, config, Some(creds), None, None); env.initialize().await.unwrap(); // Verify rg is available (installed by snapshot) @@ -929,7 +929,7 @@ async fn run_daytona_cli_test(provider: Provider, model: &str, install_command: }), ..DaytonaConfig::default() }; - let env = DaytonaSandbox::new(client, config, Some(creds), None); + let env = DaytonaSandbox::new(client, config, Some(creds), None, None); env.initialize().await.unwrap(); let env: Arc = Arc::new(env); @@ -1875,7 +1875,7 @@ async fn daytona_computer_use_browser_screenshot() { }), ..DaytonaConfig::default() }; - let env = DaytonaSandbox::new(client, config, None, None); + let env = DaytonaSandbox::new(client, config, None, None, None); env.initialize().await.unwrap(); // 1. Start the computer use desktop environment (Xvfb, xfce4, etc.) @@ -2036,7 +2036,7 @@ async fn daytona_playwright_mcp_sandbox_transport() { }), ..DaytonaConfig::default() }; - let sandbox = DaytonaSandbox::new(client, config, None, None); + let sandbox = DaytonaSandbox::new(client, config, None, None, None); sandbox.initialize().await.unwrap(); // 1. Install Playwright MCP server and its browser