diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index bab81913..98e110d8 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -2045,7 +2045,16 @@ pub async fn sandbox_create( name: name.unwrap_or_default().to_string(), }; - let response = client.create_sandbox(request).await.into_diagnostic()?; + let response = match client.create_sandbox(request).await { + Ok(resp) => resp, + Err(status) if status.code() == Code::AlreadyExists => { + return Err(miette::miette!( + "{}\n\nhint: delete it first with: openshell sandbox delete \n or use a different name", + status.message() + )); + } + Err(status) => return Err(status).into_diagnostic(), + }; let sandbox = response .into_inner() .sandbox diff --git a/crates/openshell-server/src/grpc.rs b/crates/openshell-server/src/grpc.rs index fd4bf585..911d2f09 100644 --- a/crates/openshell-server/src/grpc.rs +++ b/crates/openshell-server/src/grpc.rs @@ -244,6 +244,20 @@ impl OpenShell for OpenShellService { ..Default::default() }; + // Reject duplicate names early, before touching the index or store. + // This mirrors the provider-creation pattern (see `create_provider`). + let existing = self + .state + .store + .get_message_by_name::(&name) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))?; + if existing.is_some() { + return Err(Status::already_exists(format!( + "sandbox '{name}' already exists" + ))); + } + // Persist to the store FIRST so the sandbox watcher always finds // the record with `spec` populated. If we created the k8s // resource first, the watcher could race us and write a fallback diff --git a/e2e/python/test_inference_routing.py b/e2e/python/test_inference_routing.py index bda0d8cb..c35e0253 100644 --- a/e2e/python/test_inference_routing.py +++ b/e2e/python/test_inference_routing.py @@ -95,11 +95,6 @@ def _upsert_managed_inference( except grpc.RpcError as create_exc: if create_exc.code() == grpc.StatusCode.ALREADY_EXISTS: continue - if ( - create_exc.code() == grpc.StatusCode.INTERNAL - and "UNIQUE constraint failed" in (create_exc.details() or "") - ): - continue raise else: raise RuntimeError("failed to upsert managed e2e provider after retries")