From 399ac954d2486ed6ad25915e63895b6daa8c5964 Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Fri, 20 Mar 2026 13:20:00 -0700 Subject: [PATCH 1/2] 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 b6423aae..343653f9 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 84a323b5..6572a286 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -2058,7 +2058,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 37f11fcb..68dc4a68 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -18,7 +18,8 @@ use miette::{IntoDiagnostic, Result, WrapErr}; 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::{ @@ -2743,6 +2744,7 @@ pub async fn sandbox_delete( names: &[String], all: bool, tls: &TlsOptions, + gateway: &str, ) -> Result<()> { let mut client = grpc_client(server, tls).await?; @@ -2783,6 +2785,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 0b97d79e120bb95c93538ccadcf73bd99e009e7c Mon Sep 17 00:00:00 2001 From: Serge Panev Date: Mon, 23 Mar 2026 00:37:55 -0700 Subject: [PATCH 2/2] 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 343653f9..c350530b 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 6572a286..f2696af4 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -2058,8 +2058,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 68dc4a68..8f4c00cc 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}; 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,