mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-08 22:21:45 +00:00
Add skip_clone to DaytonaConfig to fix concurrent test failures
Two Daytona integration tests used std::env::set_current_dir to a temp directory so detect_repo_info() would fail and skip cloning. Since cwd is process-global, this poisoned concurrent tests. Replace with an explicit skip_clone config flag that skips repo detection and cloning during sandbox initialization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
44539edad2
commit
e658f9705e
4 changed files with 182 additions and 162 deletions
|
|
@ -1303,6 +1303,7 @@ mod runs {
|
|||
dockerfile: None,
|
||||
}),
|
||||
network: Some(fabro_daytona::DaytonaNetwork::Block),
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
@ -1475,6 +1476,7 @@ mod workflows {
|
|||
dockerfile: None,
|
||||
}),
|
||||
network: None,
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
@ -1548,6 +1550,7 @@ mod workflows {
|
|||
dockerfile: None,
|
||||
}),
|
||||
network: None,
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
@ -1632,6 +1635,7 @@ mod workflows {
|
|||
dockerfile: None,
|
||||
}),
|
||||
network: None,
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
@ -1707,6 +1711,7 @@ mod workflows {
|
|||
dockerfile: None,
|
||||
}),
|
||||
network: None,
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
@ -3273,6 +3278,7 @@ mod settings {
|
|||
labels: None,
|
||||
snapshot: None,
|
||||
network: Some(fabro_daytona::DaytonaNetwork::Block),
|
||||
skip_clone: false,
|
||||
}),
|
||||
exe: None,
|
||||
ssh: None,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ pub struct DaytonaConfig {
|
|||
pub labels: Option<HashMap<String, String>>,
|
||||
pub snapshot: Option<DaytonaSnapshotConfig>,
|
||||
pub network: Option<DaytonaNetwork>,
|
||||
/// Skip git repo detection and cloning during initialization.
|
||||
#[serde(default)]
|
||||
pub skip_clone: bool,
|
||||
}
|
||||
|
||||
/// Network access mode for a Daytona sandbox.
|
||||
|
|
|
|||
|
|
@ -445,176 +445,190 @@ impl Sandbox for DaytonaSandbox {
|
|||
})?;
|
||||
|
||||
// Clone the repo into the sandbox
|
||||
match detect_repo_info(&cwd) {
|
||||
Ok((detected_url, detected_branch)) => {
|
||||
// Use explicit clone_branch if provided (avoids cloning a local-only
|
||||
// worktree branch like fabro/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 {
|
||||
url: url.clone(),
|
||||
branch: branch.clone(),
|
||||
});
|
||||
let clone_start = Instant::now();
|
||||
if self.config.skip_clone {
|
||||
// Create working directory without cloning
|
||||
let fs_svc = sandbox
|
||||
.fs()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Daytona fs service: {e}"))?;
|
||||
fs_svc
|
||||
.create_folder(WORKING_DIRECTORY, None)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create working directory: {e}"))?;
|
||||
} else {
|
||||
match detect_repo_info(&cwd) {
|
||||
Ok((detected_url, detected_branch)) => {
|
||||
// Use explicit clone_branch if provided (avoids cloning a local-only
|
||||
// worktree branch like fabro/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 {
|
||||
url: url.clone(),
|
||||
branch: branch.clone(),
|
||||
});
|
||||
let clone_start = Instant::now();
|
||||
|
||||
// Resolve clone credentials via GitHub App or fall back to no auth
|
||||
let (username, password) = match &self.github_app {
|
||||
Some(creds) => {
|
||||
let (owner, repo) =
|
||||
fabro_github::parse_github_owner_repo(&url).map_err(|e| {
|
||||
let err = format!("Failed to parse GitHub URL for clone: {e}");
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: err.clone(),
|
||||
});
|
||||
err
|
||||
})?;
|
||||
fabro_github::resolve_clone_credentials(creds, &owner, &repo)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let err =
|
||||
format!("Failed to get GitHub App credentials for clone: {e}");
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms = u64::try_from(init_start.elapsed().as_millis())
|
||||
.unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
err
|
||||
})?
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
// Resolve clone credentials via GitHub App or fall back to no auth
|
||||
let (username, password) = match &self.github_app {
|
||||
Some(creds) => {
|
||||
let (owner, repo) = fabro_github::parse_github_owner_repo(&url)
|
||||
.map_err(|e| {
|
||||
let err = format!("Failed to parse GitHub URL for clone: {e}");
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: err.clone(),
|
||||
});
|
||||
err
|
||||
})?;
|
||||
fabro_github::resolve_clone_credentials(creds, &owner, &repo)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let err = format!(
|
||||
"Failed to get GitHub App credentials for clone: {e}"
|
||||
);
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis())
|
||||
.unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
err
|
||||
})?
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let git_svc = sandbox
|
||||
.git()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Daytona git service: {e}"));
|
||||
let git_svc = match git_svc {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: e.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: e.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let git_svc = sandbox
|
||||
.git()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Daytona git service: {e}"));
|
||||
let git_svc = match git_svc {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url: url.clone(),
|
||||
error: e.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: e.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let clone_token = password.clone();
|
||||
let clone_result = git_svc
|
||||
.clone(
|
||||
&url,
|
||||
WORKING_DIRECTORY,
|
||||
daytona_sdk::GitCloneOptions {
|
||||
branch,
|
||||
username,
|
||||
password,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let clone_token = password.clone();
|
||||
let clone_result = git_svc
|
||||
.clone(
|
||||
&url,
|
||||
WORKING_DIRECTORY,
|
||||
daytona_sdk::GitCloneOptions {
|
||||
branch,
|
||||
username,
|
||||
password,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
match clone_result {
|
||||
Ok(()) => {
|
||||
let clone_duration =
|
||||
u64::try_from(clone_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::GitCloneCompleted {
|
||||
url: url.clone(),
|
||||
duration_ms: clone_duration,
|
||||
});
|
||||
match clone_result {
|
||||
Ok(()) => {
|
||||
let clone_duration = u64::try_from(clone_start.elapsed().as_millis())
|
||||
.unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::GitCloneCompleted {
|
||||
url: url.clone(),
|
||||
duration_ms: clone_duration,
|
||||
});
|
||||
|
||||
// Store origin URL and set push credentials for later pushes
|
||||
if let Some(token) = clone_token {
|
||||
let _ = self.origin_url.set(url);
|
||||
let process_svc = sandbox.process().await.ok();
|
||||
if let Some(ps) = process_svc {
|
||||
let origin = self.origin_url.get().expect("just set");
|
||||
let auth_url = origin.replacen(
|
||||
"https://",
|
||||
&format!("https://x-access-token:{token}@"),
|
||||
1,
|
||||
);
|
||||
let cmd = format!(
|
||||
"git -c maintenance.auto=0 remote set-url origin {}",
|
||||
shell_quote(&auth_url),
|
||||
);
|
||||
let opts = daytona_sdk::ExecuteCommandOptions {
|
||||
cwd: Some(WORKING_DIRECTORY.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let wrapped = wrap_bash_command(&cmd);
|
||||
if let Ok(r) = ps.execute_command(&wrapped, opts).await {
|
||||
if r.exit_code != 0 {
|
||||
tracing::warn!(
|
||||
exit_code = r.exit_code,
|
||||
"Failed to set push credentials on origin"
|
||||
);
|
||||
// Store origin URL and set push credentials for later pushes
|
||||
if let Some(token) = clone_token {
|
||||
let _ = self.origin_url.set(url);
|
||||
let process_svc = sandbox.process().await.ok();
|
||||
if let Some(ps) = process_svc {
|
||||
let origin = self.origin_url.get().expect("just set");
|
||||
let auth_url = origin.replacen(
|
||||
"https://",
|
||||
&format!("https://x-access-token:{token}@"),
|
||||
1,
|
||||
);
|
||||
let cmd = format!(
|
||||
"git -c maintenance.auto=0 remote set-url origin {}",
|
||||
shell_quote(&auth_url),
|
||||
);
|
||||
let opts = daytona_sdk::ExecuteCommandOptions {
|
||||
cwd: Some(WORKING_DIRECTORY.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let wrapped = wrap_bash_command(&cmd);
|
||||
if let Ok(r) = ps.execute_command(&wrapped, opts).await {
|
||||
if r.exit_code != 0 {
|
||||
tracing::warn!(
|
||||
exit_code = r.exit_code,
|
||||
"Failed to set push credentials on origin"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) if self.github_app.is_none() => {
|
||||
let err = format!(
|
||||
"Git clone failed: {e}. If this is a private repository, \
|
||||
Err(e) if self.github_app.is_none() => {
|
||||
let err = format!(
|
||||
"Git clone failed: {e}. If this is a private repository, \
|
||||
configure a GitHub App with `fabro install` and install it \
|
||||
for your organization."
|
||||
);
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url,
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(err);
|
||||
}
|
||||
Err(e) => {
|
||||
let err = format!("Failed to clone repo into Daytona sandbox: {e}");
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url,
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(err);
|
||||
);
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url,
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(err);
|
||||
}
|
||||
Err(e) => {
|
||||
let err = format!("Failed to clone repo into Daytona sandbox: {e}");
|
||||
self.emit(SandboxEvent::GitCloneFailed {
|
||||
url,
|
||||
error: err.clone(),
|
||||
});
|
||||
let duration_ms =
|
||||
u64::try_from(init_start.elapsed().as_millis()).unwrap_or(u64::MAX);
|
||||
self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "daytona".into(),
|
||||
error: err.clone(),
|
||||
duration_ms,
|
||||
});
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Could not detect git repo for Daytona clone");
|
||||
// Create working directory even without a repo
|
||||
let fs_svc = sandbox
|
||||
.fs()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Daytona fs service: {e}"))?;
|
||||
fs_svc
|
||||
.create_folder(WORKING_DIRECTORY, None)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create working directory: {e}"))?;
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "Could not detect git repo for Daytona clone");
|
||||
// Create working directory even without a repo
|
||||
let fs_svc = sandbox
|
||||
.fs()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get Daytona fs service: {e}"))?;
|
||||
fs_svc
|
||||
.create_folder(WORKING_DIRECTORY, None)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create working directory: {e}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1852,9 +1852,6 @@ async fn daytona_cp_upload_download_round_trip() {
|
|||
async fn daytona_computer_use_browser_screenshot() {
|
||||
use base64::Engine;
|
||||
|
||||
// Run from a temp dir so detect_repo_info() finds no git repo and skips cloning.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::env::set_current_dir(tmp.path()).unwrap();
|
||||
dotenvy::dotenv().ok();
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
dotenvy::from_path(home.join(".fabro/.env")).ok();
|
||||
|
|
@ -1867,6 +1864,7 @@ async fn daytona_computer_use_browser_screenshot() {
|
|||
disk: None,
|
||||
dockerfile: None,
|
||||
}),
|
||||
skip_clone: true,
|
||||
..DaytonaConfig::default()
|
||||
};
|
||||
let env = DaytonaSandbox::new(config, None, None, None)
|
||||
|
|
@ -2013,8 +2011,6 @@ async fn daytona_playwright_mcp_sandbox_transport() {
|
|||
use fabro_agent::Sandbox;
|
||||
|
||||
// Create sandbox from daytona-medium (has Node.js + Chromium)
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::env::set_current_dir(tmp.path()).unwrap();
|
||||
dotenvy::dotenv().ok();
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
dotenvy::from_path(home.join(".fabro/.env")).ok();
|
||||
|
|
@ -2027,6 +2023,7 @@ async fn daytona_playwright_mcp_sandbox_transport() {
|
|||
disk: None,
|
||||
dockerfile: None,
|
||||
}),
|
||||
skip_clone: true,
|
||||
..DaytonaConfig::default()
|
||||
};
|
||||
let sandbox = DaytonaSandbox::new(config, None, None, None)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue