From d1ca30dd5676f201e524a01855ce865d0e91e9c1 Mon Sep 17 00:00:00 2001 From: "Harold Kim (Paperclip)" Date: Thu, 9 Jul 2026 05:55:38 +0000 Subject: [PATCH] fix: guard environment delete custom image cancellation state Add `active_runtime_use` to `EnvironmentDeleteBlockedReason` union so the blast-radius response can report active runtime blockers as a typed reason. Surface this in `getDeleteBlastRadius`: compute `hasActiveRuntimeUse` once and push `"active_runtime_use"` into `deleteBlockedReasons` when either active lease count or active custom image setup session count is non-zero. Extract `releaseActiveLeasesForEnvironment` service method so the route can mark active leases released during a forced delete. Co-Authored-By: Paperclip --- packages/shared/src/types/environment.ts | 2 +- server/src/services/environments.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/types/environment.ts b/packages/shared/src/types/environment.ts index 9e38f2d5585..48c6e971715 100644 --- a/packages/shared/src/types/environment.ts +++ b/packages/shared/src/types/environment.ts @@ -71,7 +71,7 @@ export interface Environment { updatedAt: Date; } -export type EnvironmentDeleteBlockedReason = "managed_local" | "instance_default"; +export type EnvironmentDeleteBlockedReason = "managed_local" | "instance_default" | "active_runtime_use"; export interface EnvironmentDeleteBlastRadius { environmentId: string; diff --git a/server/src/services/environments.ts b/server/src/services/environments.ts index 6487f7a1b9e..4342a12a82d 100644 --- a/server/src/services/environments.ts +++ b/server/src/services/environments.ts @@ -552,11 +552,13 @@ export function environmentService(db: Db) { const isManagedLocal = environment.driver === "local"; const isInstanceDefault = countFromRows(instanceDefaultRows) > 0; + const activeLeaseCount = countFromRows(activeLeaseRows); + const activeCustomImageSetupSessionCount = countFromRows(activeSetupRows); + const hasActiveRuntimeUse = activeLeaseCount > 0 || activeCustomImageSetupSessionCount > 0; const deleteBlockedReasons: EnvironmentDeleteBlockedReason[] = []; if (isManagedLocal) deleteBlockedReasons.push("managed_local"); if (isInstanceDefault) deleteBlockedReasons.push("instance_default"); - const activeLeaseCount = countFromRows(activeLeaseRows); - const activeCustomImageSetupSessionCount = countFromRows(activeSetupRows); + if (hasActiveRuntimeUse) deleteBlockedReasons.push("active_runtime_use"); return { environmentId: id, @@ -574,11 +576,21 @@ export function environmentService(db: Db) { activeRuntimeUse: { activeLeaseCount, activeCustomImageSetupSessionCount, - hasActiveRuntimeUse: activeLeaseCount > 0 || activeCustomImageSetupSessionCount > 0, + hasActiveRuntimeUse, }, }; }, + releaseActiveLeasesForEnvironment: async (id: string): Promise => { + const now = new Date(); + const rows = await db + .update(environmentLeases) + .set({ status: "released", releasedAt: now, lastUsedAt: now, updatedAt: now }) + .where(and(eq(environmentLeases.environmentId, id), eq(environmentLeases.status, "active"))) + .returning(); + return rows.length; + }, + listLeases: async ( environmentId: string, filters: {