Fix Daytona sandbox clone failing on unpushed worktree branch

When `arc run` uses a Daytona sandbox, `initialize()` re-detects the
branch from cwd — but by that point the worktree has switched HEAD to
`arc/run/{run_id}`, which was never pushed, causing the clone to fail
with HTTP 400. Pass the already-known base branch (e.g. `main`) from
the caller instead of re-detecting it inside the sandbox.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-10 23:11:42 -04:00
parent ecb585d2a4
commit 9245a9987b
3 changed files with 47 additions and 6 deletions

View file

@ -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<dyn Sandbox>)
}

View file

@ -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<String>,
run_id: Option<String>,
/// 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<String>,
}
impl DaytonaSandbox {
@ -169,6 +173,7 @@ impl DaytonaSandbox {
config: DaytonaConfig,
github_app: Option<GitHubAppCredentials>,
run_id: Option<String>,
clone_branch: Option<String>,
) -> 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.
}

View file

@ -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<dyn Sandbox> = 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