Point refused sandbox cleanup at the force option

A run whose sandbox cleanup fails is kept for a retry, and only an explicit
force deletes it without its sandboxes. Say so in the 409 so a caller with a
run stuck behind an unavailable provider knows how to proceed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Scott Werner 2026-10-12 13:46:57 -04:00
parent a2ca338514
commit e09a08f1bb
2 changed files with 17 additions and 11 deletions

View file

@ -2991,10 +2991,9 @@ async fn delete_run_sandbox_resource(
);
Ok(SandboxDeleteOutcome::Cleaned)
} else {
Err(ApiError::new(
StatusCode::CONFLICT,
format!("Failed to delete the run's sandboxes: {problems}"),
))
Err(sandbox_cleanup_conflict(&format!(
"Failed to delete the run's sandboxes: {problems}"
)))
}
}
// A live process still holds the run: only a forced delete leaves
@ -3021,12 +3020,22 @@ async fn delete_run_sandbox_resource(
);
Ok(SandboxDeleteOutcome::Cleaned)
} else {
Err(ApiError::new(StatusCode::CONFLICT, message))
Err(sandbox_cleanup_conflict(&message))
}
}
}
}
/// A refused cleanup keeps the run for a retry; say how to delete it anyway.
fn sandbox_cleanup_conflict(message: &str) -> ApiError {
ApiError::new(
StatusCode::CONFLICT,
format!(
"{message} (use force=true or --force to delete the run and leave its sandboxes behind)"
),
)
}
async fn reject_active_delete_without_force(
state: &AppState,
run_id: &RunId,

View file

@ -234,12 +234,9 @@ async fn failed_cleanup_preserves_completed_results_across_retries_and_restart()
for _ in 0..2 {
let response = deletion_response(&run.app, run.id, false).await;
let error = response_json!(response, StatusCode::CONFLICT).await;
assert!(
error["errors"][0]["detail"]
.as_str()
.unwrap()
.contains("Failed to delete the run's sandboxes")
);
let detail = error["errors"][0]["detail"].as_str().unwrap();
assert!(detail.contains("Failed to delete the run's sandboxes"));
assert!(detail.contains("use force=true or --force"));
assert!(workspace.is_dir());
assert_completed_result(&run.app, run.id, &before).await;
}