Default Daytona auto-stop to 120 minutes

Omitting autoStopInterval from the create-sandbox request inherits
Daytona's server-side default of 15 idle minutes. Daytona counts
inactivity from the last sandbox interaction, and LLM inference never
touches the sandbox, so a single long inference call is enough for the
sandbox to auto-stop mid-run: a workflow failed exactly this way, with
the sandbox entering its stop transition 15 minutes after the last
command while the agent was still thinking.

Send an explicit 120-minute default when lifecycle.auto_stop is unset.
That clears any realistic inference call while still reclaiming
sandboxes leaked by a dead worker. An explicit auto_stop = "0s" still
disables auto-stop entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-08-20 17:26:13 -04:00
parent 03c3412e51
commit 0845c331cb
No known key found for this signature in database
3 changed files with 40 additions and 2 deletions

View file

@ -321,7 +321,7 @@ memory = "8GB"
| `network.allow` | CIDRs for `cidr_allow_list`; entries are validated as CIDRs. |
| `lifecycle.preserve` | Keep the created sandbox after the run finishes. |
| `lifecycle.stop_on_terminal` | Stop the sandbox when the run reaches a terminal state. |
| `lifecycle.auto_stop` | Daytona auto-stop duration, such as `"30m"`. |
| `lifecycle.auto_stop` | Daytona auto-stop duration, such as `"30m"`. Defaults to `"120m"`; `"0s"` disables auto-stop. |
| `labels` | Provider labels. Merge by key across layers. |
| `env` | Environment variables passed to command and agent execution. Merge by key across layers. |

View file

@ -198,6 +198,10 @@ The `lifecycle.auto_stop` setting tells Daytona to stop the sandbox after a peri
auto_stop = "30m"
```
When `auto_stop` is unset, Fabro applies a default of 120 minutes so a sandbox leaked by an interrupted run is still reclaimed. Set `auto_stop = "0s"` to disable auto-stop and let the sandbox run indefinitely.
Daytona counts inactivity from the last sandbox interaction (a command, file operation, or other API call). Time an agent spends on LLM inference does not touch the sandbox, so intervals shorter than your longest inference call risk stopping the sandbox mid-run.
## Server defaults
When running via `fabro server start`, the server config at `~/.fabro/settings.toml` can set default Daytona settings for all runs. Run config TOML values override server defaults. Labels are **merged** — run config labels win on key collisions. The `network` setting uses simple override (run config replaces the server default entirely).

View file

@ -68,6 +68,12 @@ const DAYTONA_START_TIMEOUT: Duration = Duration::from_mins(1);
/// deletion, temporary stdin files) so a stalled REST call cannot block
/// cancellation/timeout paths indefinitely.
const DAYTONA_CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
/// Auto-stop applied when `lifecycle.auto_stop` is unset. Omitting the field
/// would inherit Daytona's server-side default of 15 idle minutes, which is
/// shorter than a single long inference call and stops the sandbox mid-run;
/// 120 minutes clears any realistic call while still reclaiming sandboxes
/// leaked by a dead worker. An explicit `0` disables auto-stop entirely.
const DEFAULT_AUTO_STOP_INTERVAL_MINUTES: i32 = 120;
/// Permissions a Daytona API key needs for Fabro's snapshot and sandbox flow.
pub const REQUIRED_DAYTONA_PERMISSIONS: &[Permissions] = &[
@ -727,7 +733,10 @@ impl DaytonaSandbox {
daytona_sdk::SandboxBaseParams {
name: Some(name),
env_vars: Some(clean_bash_env(None)),
auto_stop_interval: self.config.auto_stop_interval,
auto_stop_interval: self
.config
.auto_stop_interval
.or(Some(DEFAULT_AUTO_STOP_INTERVAL_MINUTES)),
labels: Some(managed_labels::merge_for_run(
self.config.labels.as_ref(),
self.run_id.as_ref(),
@ -2950,6 +2959,10 @@ mod tests {
assert_eq!(params.ephemeral, Some(false));
assert_eq!(params.auto_delete_interval, Some(-1));
assert_eq!(
params.auto_stop_interval,
Some(DEFAULT_AUTO_STOP_INTERVAL_MINUTES)
);
assert_eq!(
params.env_vars,
Some(HashMap::from([(BASH_ENV_VAR.to_string(), String::new())]))
@ -2963,6 +2976,27 @@ mod tests {
);
}
#[tokio::test]
async fn base_params_passes_explicit_auto_stop_through() {
for interval in [0, 45] {
let sandbox = DaytonaSandbox::new(
DaytonaConfig {
auto_stop_interval: Some(interval),
..DaytonaConfig::default()
},
None,
None,
None,
None,
Some("dtn_test".to_string()),
)
.await
.expect("sandbox config should be valid");
assert_eq!(sandbox.base_params().auto_stop_interval, Some(interval));
}
}
#[tokio::test]
async fn activate_skips_start_when_daytona_reports_started() {
let server = MockServer::start_async().await;