Collapse the run sandbox's lifecycle surface

RunSandbox grew its lifecycle methods one adopter at a time and ended up
with several names for each step. Reconnecting from a run record had
four entry points (reconnect, reconnect_for_run,
reconnect_for_run_with_events, reconnect_driver_for_run) that all
forwarded to the last one. Bringing a sandbox back had two (start and
activate) over the same make_ready, and releasing it had two (delete and
cleanup) over the same release. Two more methods had no callers at all:
set_autostop_interval, which nothing set after the driver took over
lifecycle timers, and resume_setup_commands, which resume stopped using
when checkout moved to the git facet.

There is now one of each. reconnect_for_run takes the record, the
provider access, an optional run id, and an optional event context;
callers that need none pass None. activate is the single "make usable"
step: a running sandbox only learns its platform when it has not yet, a
stopped or paused one is started and its Bash verified, and resume calls
it like every access-time caller. delete is the single release; for a
designated host directory it frees the handle and leaves the directory in
place, as cleanup did. The tests and server call sites follow the
renames; behavior is unchanged except that resuming an already running
sandbox no longer re-runs the Bash probe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-11 15:05:06 -06:00
parent 8771ef6d8e
commit 981253f990
No known key found for this signature in database
19 changed files with 74 additions and 135 deletions

View file

@ -1181,7 +1181,7 @@ async fn reconnect_run_sandbox(
.provider_access()
.await
.map_err(|err| ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
let sandbox = reconnect_for_run(&record, &access, Some(*run_id))
let sandbox = reconnect_for_run(&record, &access, Some(*run_id), None)
.await
.map_err(|err| ApiError::new(StatusCode::CONFLICT, err.to_string()))?;
sandbox

View file

@ -1000,7 +1000,7 @@ async fn run_sandbox_check(
warn: true,
});
}
if let Err(err) = sandbox.cleanup().await {
if let Err(err) = sandbox.delete().await {
checks.push(CheckResult {
name: "Sandbox".into(),
status: CheckStatus::Error,
@ -1020,7 +1020,7 @@ async fn run_sandbox_check(
true
}
Err(err) => {
let cleanup_error = sandbox.cleanup().await.err();
let cleanup_error = sandbox.delete().await.err();
checks.push(CheckResult {
name: "Sandbox".into(),
status: CheckStatus::Error,

View file

@ -2775,7 +2775,7 @@ async fn delete_run_sandbox_resource(
.provider_access()
.await
.map_err(|err| ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
let sandbox = match reconnect_for_run(&record, &access, Some(id)).await {
let sandbox = match reconnect_for_run(&record, &access, Some(id), None).await {
Ok(sandbox) => sandbox,
Err(err) if force || delete_started => {
tracing::warn!(

View file

@ -5,7 +5,7 @@ use std::time::Duration;
use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade};
use fabro_sandbox::{
FileKind, ProviderAccess, PtySize, RunSandbox, open_terminal_for_run, reconnect_driver_for_run,
FileKind, ProviderAccess, PtySize, RunSandbox, open_terminal_for_run, reconnect_for_run,
};
use fabro_types::{RunSandboxInstance, SandboxProviderKind};
use futures_util::FutureExt;
@ -735,7 +735,7 @@ async fn reconnect_run_sandbox_instance(
record: &RunSandboxInstance,
) -> Result<RunSandbox, Response> {
let access = load_provider_access(state).await?;
let sandbox = reconnect_driver_for_run(record, &access, Some(*run_id), None)
let sandbox = reconnect_for_run(record, &access, Some(*run_id), None)
.await
.map_err(|err| {
let detail = render_with_causes(&err.to_string(), &collect_causes(err.as_ref()));

View file

@ -721,7 +721,7 @@ async fn build_agent_session(
.provider_access()
.await
.map_err(|err| AskFabroBuildError::Agent(anyhow::Error::new(err)))?;
let sandbox = reconnect_for_run(sandbox_instance, &access, Some(run_id))
let sandbox = reconnect_for_run(sandbox_instance, &access, Some(run_id), None)
.await
.map_err(AskFabroBuildError::SandboxUnavailable)?;
sandbox

View file

@ -60,7 +60,7 @@ async fn shell_reports_real_docker_process_outcome() {
)
.await;
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");

View file

@ -435,6 +435,6 @@ mod wire_gate {
);
};
checks.await;
sandbox.cleanup().await.expect("cleanup");
sandbox.delete().await.expect("cleanup");
}
}

View file

@ -11,7 +11,7 @@ pub async fn sandbox_details(
access: &ProviderAccess,
run_id: Option<RunId>,
) -> Result<SandboxDetails> {
let sandbox = reconnect::reconnect_driver_for_run(record, access, run_id, None).await?;
let sandbox = reconnect::reconnect_for_run(record, access, run_id, None).await?;
let status = sandbox.handle()?.describe().await.map_err(|err| {
anyhow::anyhow!(
"Failed to describe {} sandbox '{}': {err}",

View file

@ -19,11 +19,10 @@ use std::time::{Duration, Instant};
use fabro_github::GitHubCredentials;
use fabro_github::token_source::{InstallationTokenSource, TokenSnapshot};
use fabro_types::SandboxProviderKind;
use fabro_util::shell;
use fabro_util::workspace_glob::WorkspaceGlob;
use sandbox_driver::{
DirEntry, EventContext, ExecControls, ExecResult, ExecSpec, ExecStreamingResult, FileKind,
GitRetryPolicy, GrepMatch, GrepOptions, LifecycleTimers, PtyOptions, PtySession, PtySize,
GitRetryPolicy, GrepMatch, GrepOptions, PtyOptions, PtySession, PtySize,
Sandbox as DriverHandle, SandboxProvider as DriverProvider, SandboxSource,
SandboxSpec as DriverSpec, SandboxState, Search as _, StdioProcess, WaitOptions, WalkOptions,
};
@ -480,7 +479,7 @@ impl RunSandbox {
}
/// Bring the sandbox to `Running` with a verified Bash, and learn its
/// platform. Shared by initialize and start.
/// platform. Shared by initialize and activate.
async fn make_ready(&self) -> crate::Result<()> {
sandbox_driver::activate(self.handle()?.as_ref(), &WaitOptions::default()).await?;
self.learn_platform().await
@ -883,32 +882,27 @@ impl RunSandbox {
.and_then(|status| status.web_url)
}
/// Idempotent access-time check: a running sandbox is left alone; a
/// stopped or paused one is brought back and its Bash verified.
/// Brings the sandbox back into use, idempotently: a running sandbox is
/// left alone and only its platform is learned when unknown; a stopped
/// or paused one is started and its Bash verified. Resume and every
/// access-time caller share this one entry point.
pub async fn activate(&self) -> crate::Result<()> {
let status = self.handle()?.describe().await?;
if status.state == SandboxState::Running {
return Ok(());
return self.learn_platform().await;
}
self.make_ready().await
}
pub async fn start(&self) -> crate::Result<()> {
self.make_ready().await
}
pub async fn stop(&self) -> crate::Result<()> {
self.handle()?.stop().await.map_err(crate::Error::from)
}
pub async fn delete(&self) -> crate::Result<()> {
self.release().await
}
/// Releases the sandbox. For a designated host directory this frees the
/// handle and leaves the directory in place; for an isolated provider it
/// removes the sandbox.
pub async fn cleanup(&self) -> crate::Result<()> {
/// removes the sandbox. A pending sandbox that was never created has
/// nothing to release.
pub async fn delete(&self) -> crate::Result<()> {
self.release().await
}
@ -964,22 +958,6 @@ impl RunSandbox {
self.workspace.as_ref().and_then(RepoWorkspace::record)
}
pub async fn set_autostop_interval(&self, minutes: i32) -> crate::Result<()> {
let mut timers = LifecycleTimers::default();
timers.auto_stop_after_idle = u64::try_from(minutes)
.ok()
.filter(|minutes| *minutes > 0)
.map(Duration::from_mins);
match self.handle()?.set_timers(&timers).await {
// A provider without timers has nothing to stop automatically.
Ok(()) | Err(sandbox_driver::Error::Unsupported { .. }) => Ok(()),
Err(error) => Err(crate::Error::context(
"Failed to set sandbox auto-stop",
error,
)),
}
}
pub async fn setup_git(&self, intent: &GitSetupIntent) -> crate::Result<Option<GitRunInfo>> {
if !self.repo_cloned() {
return Ok(None);
@ -987,17 +965,6 @@ impl RunSandbox {
sandbox::setup_git(self, intent).await.map(Some)
}
pub fn resume_setup_commands(&self, run_branch: &str) -> Vec<String> {
if !self.repo_cloned() {
return Vec::new();
}
vec![format!(
"git fetch origin {} && git checkout {}",
shell::shell_quote(run_branch),
shell::shell_quote(run_branch)
)]
}
pub async fn git_push_ref(
&self,
refspec: &str,
@ -1388,7 +1355,7 @@ mod tests {
sandbox.stop().await.unwrap();
sandbox.activate().await.unwrap();
sandbox.cleanup().await.unwrap();
sandbox.delete().await.unwrap();
assert!(
dir.path().is_dir(),
"designated directories survive cleanup"
@ -1440,7 +1407,7 @@ mod tests {
Path::new(sandbox.working_directory()),
workspace.canonicalize().unwrap()
);
sandbox.cleanup().await.unwrap();
sandbox.delete().await.unwrap();
assert!(workspace.is_dir());
}

View file

@ -50,10 +50,7 @@ pub use git_policy::{
};
pub use provider::{SandboxInventory, SandboxLookupError};
pub use provider_sandbox::{attach_provider_sandbox, provider_sandbox};
pub use reconnect::{
open_terminal_for_run, reconnect, reconnect_driver_for_run, reconnect_for_run,
reconnect_for_run_with_events,
};
pub use reconnect::{open_terminal_for_run, reconnect_for_run};
pub use sandbox::{
DEFAULT_EXEC_OUTPUT_TAIL_BYTES, GitRunInfo, GitSetupIntent, PushAttempt, PushError, PushReport,
SandboxFile, SandboxWorkspaceLayout, redacted_output_tail, setup_git,

View file

@ -9,38 +9,16 @@ use crate::driver::ProviderAccess;
use crate::driver_sandbox::RunSandbox;
use crate::provider_sandbox;
/// Reconnect to a sandbox from a saved record.
/// Reconnect to a run's sandbox from its saved record.
///
/// `access` carries the provider settings and vault credentials the record's
/// provider needs; the process environment is never consulted.
pub async fn reconnect(record: &RunSandboxInstance, access: &ProviderAccess) -> Result<RunSandbox> {
reconnect_for_run(record, access, None).await
}
/// provider needs; the process environment is never consulted. `run_id`
/// narrows the ownership scope to the run when known, and the driver reports
/// the sandbox's lifecycle from here on through `events`.
pub async fn reconnect_for_run(
record: &RunSandboxInstance,
access: &ProviderAccess,
run_id: Option<RunId>,
) -> Result<RunSandbox> {
reconnect_for_run_with_events(record, access, run_id, None).await
}
pub async fn reconnect_for_run_with_events(
record: &RunSandboxInstance,
access: &ProviderAccess,
run_id: Option<RunId>,
events: Option<EventContext>,
) -> Result<RunSandbox> {
reconnect_driver_for_run(record, access, run_id, events).await
}
/// Reconnects as the driver-backed sandbox type, for callers that need a
/// driver facet fabro's [`Sandbox`](crate::Sandbox) trait does not carry
/// (VNC, signed previews, leased SSH).
pub async fn reconnect_driver_for_run(
record: &RunSandboxInstance,
access: &ProviderAccess,
run_id: Option<RunId>,
events: Option<EventContext>,
) -> Result<RunSandbox> {
let runtime = &record.runtime;
@ -84,7 +62,7 @@ pub async fn open_terminal_for_run(
run_id: Option<RunId>,
size: PtySize,
) -> crate::Result<Box<dyn PtySession>> {
let sandbox = reconnect_driver_for_run(record, access, run_id, None)
let sandbox = reconnect_for_run(record, access, run_id, None)
.await
.map_err(|err| crate::Error::context_anyhow("Failed to reconnect sandbox", err))?;
sandbox.activate().await?;

View file

@ -43,7 +43,7 @@ mod daytona_streaming_live {
sandbox.initialize().await?;
let smoke_result = run_smoke(Arc::clone(&sandbox)).await;
let cleanup_result = sandbox.cleanup().await.context("clean up Daytona sandbox");
let cleanup_result = sandbox.delete().await.context("clean up Daytona sandbox");
smoke_result?;
cleanup_result?;
@ -144,7 +144,7 @@ mod daytona_streaming_live {
}
.await;
let cleanup_result = sandbox.cleanup().await.context("clean up Daytona sandbox");
let cleanup_result = sandbox.delete().await.context("clean up Daytona sandbox");
checks?;
cleanup_result?;
@ -182,7 +182,7 @@ mod daytona_streaming_live {
.await
.context("describe sandbox")?
.labels;
let cleanup_result = sandbox.cleanup().await.context("clean up Daytona sandbox");
let cleanup_result = sandbox.delete().await.context("clean up Daytona sandbox");
ensure_eq(
&labels.get("sh.fabro.managed").map(String::as_str),
@ -246,7 +246,7 @@ mod daytona_streaming_live {
None,
)
.await?;
let cleanup_result = sandbox.cleanup().await.context("clean up Daytona sandbox");
let cleanup_result = sandbox.delete().await.context("clean up Daytona sandbox");
ensure!(
result.success(),
@ -292,7 +292,7 @@ mod daytona_streaming_live {
sandbox.initialize().await?;
let glob_result = run_glob_checks(&sandbox).await;
let cleanup_result = sandbox.cleanup().await.context("clean up Daytona sandbox");
let cleanup_result = sandbox.delete().await.context("clean up Daytona sandbox");
glob_result?;
cleanup_result?;

View file

@ -92,7 +92,7 @@ async fn streaming_timeout_terminates_docker_exec_before_returning() {
.await
.expect("process probe should run");
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");
@ -144,7 +144,7 @@ async fn streaming_command_receives_exact_stdin_and_eof() {
.expect("injection probe should run");
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");
@ -207,7 +207,7 @@ async fn cloned_docker_sandbox_uses_repos_checkout_and_workspace_symlink() {
.await
.expect("layout verification command should run");
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");
@ -289,7 +289,7 @@ async fn docker_runs_clean_bash_through_both_command_paths() {
.expect("streaming command should run");
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");
@ -363,7 +363,7 @@ async fn docker_glob_matches_patterns_containing_a_path_separator() {
let recursive = sandbox.glob("**/SKILL.md", Some("skills")).await;
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");
@ -450,7 +450,7 @@ async fn docker_runtime_directory_is_private_and_outside_workspace() {
let readback = sandbox.read_file_text(&blob_path).await;
sandbox
.cleanup()
.delete()
.await
.expect("docker cleanup should succeed");

View file

@ -374,7 +374,7 @@ async fn agent_tool_call_latency_through_the_driver() {
fabro_docker.initialize().await.expect("fabro docker init");
unpack_fabro(&fabro_docker, &repo).await;
rows.extend(bench_fabro("fabro Docker (driver-backed)", &fabro_docker, &repo).await);
fabro_docker.cleanup().await.expect("fabro docker cleanup");
fabro_docker.delete().await.expect("fabro docker cleanup");
let docker_provider = Arc::new(DockerProvider::connect().await.expect("docker connect"));
let container = docker_provider

View file

@ -77,9 +77,8 @@ fn parse_refresh_enabled(raw: Option<&str>) -> bool {
)
}
/// Parse the refresh-ahead loop interval. `None` disables the loop (explicit
/// `0`, mirroring the codebase's `set_autostop_interval` "0 to disable"
/// convention). Unset/empty or an unparsable value falls back to the default.
/// Parse the refresh-ahead loop interval. `None` disables the loop (an
/// explicit `0`). Unset/empty or an unparsable value falls back to the default.
fn parse_refresh_interval(raw: Option<&str>) -> Option<Duration> {
match raw.map(str::trim) {
None | Some("") => Some(REFRESH_INTERVAL_DEFAULT),

View file

@ -12,7 +12,7 @@ use fabro_llm::credentials::{CredentialProvider, readiness};
use fabro_llm::lithos_catalog::Catalog;
use fabro_sandbox::{
DaytonaCredentials, ExecResultExt, GitSetupIntent, ProviderAccess, SandboxSpec,
reconnect_for_run_with_events,
reconnect_for_run,
};
use fabro_static::EnvVars;
use fabro_types::RunSandboxKind;
@ -433,7 +433,7 @@ pub async fn initialize(
DaytonaCredentials::from_api_key(api_key.to_string(), process_env_var)
}),
};
let sandbox = reconnect_for_run_with_events(
let sandbox = reconnect_for_run(
&instance,
&access,
Some(options.run_options.run_id),
@ -460,10 +460,8 @@ pub async fn initialize(
});
if attach_existing {
// Resume needs the full provider health check. `activate()` is the
// lighter access-time operation used after a run is already active.
sandbox
.start()
.activate()
.await
.map_err(|e| Error::engine_with_source("Failed to start sandbox", e))?;
} else {

View file

@ -14,7 +14,7 @@
reason = "This integration test stages sandbox fixtures with sync std::fs."
)]
use fabro_sandbox::reconnect::reconnect;
use fabro_sandbox::reconnect::reconnect_for_run;
use fabro_sandbox::{CloneRequest, ProviderAccess, provider_sandbox};
use fabro_types::{RunSandboxInstance, RunSandboxRuntime, SandboxProviderKind};
use sandbox_driver::{SandboxSource, SandboxSpec};
@ -50,7 +50,7 @@ async fn local_cp_upload_download_round_trip() {
let scratch = tempfile::tempdir().unwrap();
let record = local_record(sandbox_dir.path());
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect local");
@ -83,7 +83,7 @@ async fn local_cp_binary_round_trip() {
let scratch = tempfile::tempdir().unwrap();
let record = local_record(sandbox_dir.path());
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect local");
@ -112,7 +112,7 @@ async fn local_cp_creates_parent_dirs() {
let scratch = tempfile::tempdir().unwrap();
let record = local_record(sandbox_dir.path());
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect local");
@ -237,7 +237,7 @@ async fn docker_cp_upload_download_round_trip() {
let scratch = tempfile::tempdir().unwrap();
let record = docker_record(&container.id);
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect docker");
@ -268,7 +268,7 @@ async fn docker_cp_binary_round_trip() {
let scratch = tempfile::tempdir().unwrap();
let record = docker_record(&container.id);
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect docker");
@ -297,7 +297,7 @@ async fn docker_cp_creates_parent_dirs() {
let scratch = tempfile::tempdir().unwrap();
let record = docker_record(&container.id);
let sandbox = reconnect(&record, &ProviderAccess::default())
let sandbox = reconnect_for_run(&record, &ProviderAccess::default(), None, None)
.await
.expect("reconnect docker");

View file

@ -278,7 +278,7 @@ async fn daytona_exec_command() {
assert_eq!(result.exit_code, Some(0));
assert!(result.stdout_lossy().contains("hello"));
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -294,7 +294,7 @@ async fn daytona_exec_command_with_pipe() {
assert_eq!(result.exit_code, Some(0));
assert!(result.stdout_lossy().trim().contains('2'));
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -325,7 +325,7 @@ async fn daytona_exec_command_cancelled() {
));
assert_eq!(result.stderr_lossy(), "Command cancelled");
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -358,7 +358,7 @@ async fn daytona_exec_command_local_timeout() {
assert_eq!(result.termination, fabro_sandbox::Termination::TimedOut);
assert_eq!(result.stderr_lossy(), "Command timed out locally");
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -383,7 +383,7 @@ async fn daytona_file_round_trip() {
env.delete_file(test_path).await.unwrap();
assert!(!env.file_exists(test_path).await.unwrap());
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -408,7 +408,7 @@ async fn daytona_full_lifecycle() {
assert!(!entries.is_empty());
// Cleanup (deletes sandbox)
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -446,7 +446,7 @@ async fn daytona_snapshot_sandbox() {
assert_eq!(result.exit_code, Some(0));
assert!(result.stdout_lossy().contains("ripgrep"));
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -498,7 +498,7 @@ async fn daytona_artifact_sync_uploads_and_rewrites_pointer() {
remote_content.len()
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
// ---------------------------------------------------------------------------
@ -612,7 +612,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
"offloaded value should round-trip through the run store"
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
// ---------------------------------------------------------------------------
@ -823,7 +823,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
"checkpoint should have git_commit_sha"
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
// ---------------------------------------------------------------------------
@ -971,7 +971,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
"sandbox commit should have Fabro-Run trailer, got:\n{commit_msg}"
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
// ---------------------------------------------------------------------------
@ -1104,7 +1104,7 @@ async fn daytona_asset_collection() {
"artifact scratch cache should not be created"
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -1123,7 +1123,7 @@ async fn daytona_ssh_access() {
"ssh_command should contain 'ssh': {ssh_command}",
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
@ -1204,7 +1204,7 @@ async fn daytona_clone_private_repo_with_github_app_iat() {
result.stdout_lossy().trim()
);
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
/// E2E: Verify that repos in an installed org get credentials (needed for
@ -1390,7 +1390,7 @@ async fn daytona_git_push_run_branch_to_origin() {
}
}
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
/// Diagnose toolbox proxy staleness after idle time.
@ -1528,7 +1528,7 @@ async fn daytona_toolbox_idle_diagnostic() {
}
eprintln!("\n=== PASS: all idle durations survived ===");
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
/// E2E test for `fabro cp` against a live Daytona sandbox.
@ -1537,7 +1537,7 @@ async fn daytona_toolbox_idle_diagnostic() {
/// uploads a file, downloads it back, and verifies the round-trip.
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"), live("GITHUB_APP_PRIVATE_KEY"))]
async fn daytona_cp_upload_download_round_trip() {
use fabro_sandbox::reconnect::reconnect;
use fabro_sandbox::reconnect::reconnect_for_run;
use fabro_types::RunSandboxInstance;
// 1. Create and initialize a real Daytona sandbox
@ -1574,7 +1574,7 @@ async fn daytona_cp_upload_download_round_trip() {
daytona: Some(live_daytona_credentials()),
..ProviderAccess::default()
};
let reconnected = reconnect(&record, &access)
let reconnected = reconnect_for_run(&record, &access, None, None)
.await
.expect("reconnect should succeed");
@ -1632,7 +1632,7 @@ async fn daytona_cp_upload_download_round_trip() {
);
// 9. Cleanup
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"))]
@ -1772,7 +1772,7 @@ async fn daytona_computer_use_browser_screenshot() {
);
// 7. Cleanup
env.cleanup().await.unwrap();
env.delete().await.unwrap();
}
#[fabro_macros::e2e_test(live("DAYTONA_API_KEY"))]
@ -2005,5 +2005,5 @@ async fn daytona_playwright_mcp_sandbox_transport() {
}
// 8. Cleanup
sandbox.cleanup().await.unwrap();
sandbox.delete().await.unwrap();
}

View file

@ -13727,7 +13727,7 @@ async fn asset_collection_docker_sandbox() {
"artifact scratch cache should not be created"
);
sandbox.cleanup().await.unwrap();
sandbox.delete().await.unwrap();
}
#[tokio::test]