From 8406ae74479384a4ee36f804656074b91c27439f Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Fri, 20 Mar 2026 13:20:00 -0700 Subject: [PATCH 1/3] fix(cli): clear stale last-used sandbox on deletion When a sandbox is deleted, the locally stored last-used record now gets cleared if it matches the deleted sandbox name. This prevents subsequent commands from falling back to a sandbox that no longer exists, which previously caused confusing gRPC errors. Adds clear_last_sandbox_if_matches() to openshell-bootstrap and calls it from sandbox_delete() after each successful deletion. Closes #172 Signed-off-by: Serge Panev --- crates/openshell-bootstrap/src/lib.rs | 5 +++-- crates/openshell-bootstrap/src/metadata.rs | 14 ++++++++++++++ crates/openshell-cli/src/main.rs | 3 ++- crates/openshell-cli/src/run.rs | 5 ++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/openshell-bootstrap/src/lib.rs b/crates/openshell-bootstrap/src/lib.rs index 9098fd4a..39cfa30b 100644 --- a/crates/openshell-bootstrap/src/lib.rs +++ b/crates/openshell-bootstrap/src/lib.rs @@ -49,8 +49,9 @@ pub use crate::docker::{ }; pub use crate::metadata::{ GatewayMetadata, clear_active_gateway, extract_host_from_ssh_destination, get_gateway_metadata, - list_gateways, load_active_gateway, load_gateway_metadata, load_last_sandbox, - remove_gateway_metadata, resolve_ssh_hostname, save_active_gateway, save_last_sandbox, + clear_last_sandbox_if_matches, list_gateways, load_active_gateway, load_gateway_metadata, + load_last_sandbox, remove_gateway_metadata, resolve_ssh_hostname, save_active_gateway, + save_last_sandbox, store_gateway_metadata, }; diff --git a/crates/openshell-bootstrap/src/metadata.rs b/crates/openshell-bootstrap/src/metadata.rs index bd49ba8c..15f79c08 100644 --- a/crates/openshell-bootstrap/src/metadata.rs +++ b/crates/openshell-bootstrap/src/metadata.rs @@ -271,6 +271,20 @@ pub fn load_last_sandbox(gateway: &str) -> Option { if name.is_empty() { None } else { Some(name) } } +/// Clear the last-used sandbox record for a gateway if it matches the given name. +/// +/// This should be called after a sandbox is deleted so that subsequent commands +/// don't try to connect to a sandbox that no longer exists. +pub fn clear_last_sandbox_if_matches(gateway: &str, sandbox: &str) { + if let Some(current) = load_last_sandbox(gateway) { + if current == sandbox { + if let Ok(path) = last_sandbox_path(gateway) { + let _ = std::fs::remove_file(path); + } + } + } +} + /// List all gateways that have stored metadata. /// /// Scans `$XDG_CONFIG_HOME/openshell/gateways/` for subdirectories containing diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 3799b392..9f4b46c4 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -2268,7 +2268,8 @@ async fn main() -> Result<()> { run::sandbox_list(endpoint, limit, offset, ids, names, &tls).await?; } SandboxCommands::Delete { names, all } => { - run::sandbox_delete(endpoint, &names, all, &tls).await?; + run::sandbox_delete(endpoint, &names, all, &tls, &ctx.name) + .await?; } SandboxCommands::Connect { name, editor } => { let name = resolve_sandbox_name(name, &ctx.name)?; diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index a4331ee5..76d5b350 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -18,7 +18,8 @@ use miette::{IntoDiagnostic, Result, WrapErr, miette}; use openshell_bootstrap::{ DeployOptions, GatewayMetadata, RemoteOptions, clear_active_gateway, container_name, extract_host_from_ssh_destination, get_gateway_metadata, list_gateways, load_active_gateway, - remove_gateway_metadata, resolve_ssh_hostname, save_active_gateway, save_last_sandbox, + clear_last_sandbox_if_matches, remove_gateway_metadata, resolve_ssh_hostname, + save_active_gateway, save_last_sandbox, store_gateway_metadata, }; use openshell_core::proto::{ @@ -2824,6 +2825,7 @@ pub async fn sandbox_delete( names: &[String], all: bool, tls: &TlsOptions, + gateway: &str, ) -> Result<()> { let mut client = grpc_client(server, tls).await?; @@ -2864,6 +2866,7 @@ pub async fn sandbox_delete( let deleted = response.into_inner().deleted; if deleted { + clear_last_sandbox_if_matches(gateway, name); println!("{} Deleted sandbox {name}", "✓".green().bold()); } else { println!("{} Sandbox {name} not found", "!".yellow()); From 19235f03aae1b7c43ca0417901477d5e6756d86b Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Mon, 23 Mar 2026 00:37:55 -0700 Subject: [PATCH 2/3] style: fix rustfmt import ordering and line wrapping Signed-off-by: Serge Panev --- crates/openshell-bootstrap/src/lib.rs | 9 ++++----- crates/openshell-cli/src/main.rs | 3 +-- crates/openshell-cli/src/run.rs | 9 ++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/openshell-bootstrap/src/lib.rs b/crates/openshell-bootstrap/src/lib.rs index 39cfa30b..93898675 100644 --- a/crates/openshell-bootstrap/src/lib.rs +++ b/crates/openshell-bootstrap/src/lib.rs @@ -48,11 +48,10 @@ pub use crate::docker::{ DockerPreflight, ExistingGatewayInfo, check_docker_available, create_ssh_docker_client, }; pub use crate::metadata::{ - GatewayMetadata, clear_active_gateway, extract_host_from_ssh_destination, get_gateway_metadata, - clear_last_sandbox_if_matches, list_gateways, load_active_gateway, load_gateway_metadata, - load_last_sandbox, remove_gateway_metadata, resolve_ssh_hostname, save_active_gateway, - save_last_sandbox, - store_gateway_metadata, + GatewayMetadata, clear_active_gateway, clear_last_sandbox_if_matches, + extract_host_from_ssh_destination, get_gateway_metadata, list_gateways, load_active_gateway, + load_gateway_metadata, load_last_sandbox, remove_gateway_metadata, resolve_ssh_hostname, + save_active_gateway, save_last_sandbox, store_gateway_metadata, }; /// Options for remote SSH deployment. diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 9f4b46c4..5de31c79 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -2268,8 +2268,7 @@ async fn main() -> Result<()> { run::sandbox_list(endpoint, limit, offset, ids, names, &tls).await?; } SandboxCommands::Delete { names, all } => { - run::sandbox_delete(endpoint, &names, all, &tls, &ctx.name) - .await?; + run::sandbox_delete(endpoint, &names, all, &tls, &ctx.name).await?; } SandboxCommands::Connect { name, editor } => { let name = resolve_sandbox_name(name, &ctx.name)?; diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 76d5b350..9ee73d1d 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -16,11 +16,10 @@ use hyper_util::{client::legacy::Client, rt::TokioExecutor}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use miette::{IntoDiagnostic, Result, WrapErr, miette}; use openshell_bootstrap::{ - DeployOptions, GatewayMetadata, RemoteOptions, clear_active_gateway, container_name, - extract_host_from_ssh_destination, get_gateway_metadata, list_gateways, load_active_gateway, - clear_last_sandbox_if_matches, remove_gateway_metadata, resolve_ssh_hostname, - save_active_gateway, save_last_sandbox, - store_gateway_metadata, + DeployOptions, GatewayMetadata, RemoteOptions, clear_active_gateway, + clear_last_sandbox_if_matches, container_name, extract_host_from_ssh_destination, + get_gateway_metadata, list_gateways, load_active_gateway, remove_gateway_metadata, + resolve_ssh_hostname, save_active_gateway, save_last_sandbox, store_gateway_metadata, }; use openshell_core::proto::{ ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, ClearDraftChunksRequest, From fc40fefad8f7dff8b5e2b610514527ccd7a93ca4 Mon Sep 17 00:00:00 2001 From: John Myers Date: Mon, 23 Mar 2026 15:40:01 -0700 Subject: [PATCH 3/3] fix(cli): pass gateway to sandbox_delete in finalize_sandbox_create_session Signed-off-by: John Myers --- crates/openshell-cli/src/run.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 9ee73d1d..e32eec2a 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -1904,13 +1904,14 @@ async fn finalize_sandbox_create_session( persist: bool, session_result: Result<()>, tls: &TlsOptions, + gateway: &str, ) -> Result<()> { if persist { return session_result; } let names = [sandbox_name.to_string()]; - if let Err(err) = sandbox_delete(server, &names, false, tls).await { + if let Err(err) = sandbox_delete(server, &names, false, tls, gateway).await { if session_result.is_ok() { return Err(err); } @@ -2380,6 +2381,7 @@ pub async fn sandbox_create( persist, connect_result, &effective_tls, + gateway_name, ) .await; } @@ -2415,6 +2417,7 @@ pub async fn sandbox_create( persist, exec_result, &effective_tls, + gateway_name, ) .await }