From 5339977206a84f12d692adc0ede0d67f6da54ec0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 14 Aug 2026 07:52:20 +0000 Subject: [PATCH] fix(runtime): retain unmatched active services Co-Authored-By: Paperclip --- .../execution-workspaces-service.test.ts | 7 ++++ .../workspace-runtime-read-model.test.ts | 39 +++++++++++++++++++ .../services/workspace-runtime-read-model.ts | 10 ++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/execution-workspaces-service.test.ts b/server/src/__tests__/execution-workspaces-service.test.ts index f64ce055bac..91f5080b351 100644 --- a/server/src/__tests__/execution-workspaces-service.test.ts +++ b/server/src/__tests__/execution-workspaces-service.test.ts @@ -2599,6 +2599,13 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { providerRef: worktreePath, branchName: "feature/recorded", baseRef: "main", + metadata: { + config: { + workspaceRuntime: { + services: [{ name: "renamed-web", command: "pnpm renamed-dev" }], + }, + }, + }, }); await db.insert(workspaceRuntimeServices).values({ id: runtimeServiceId, diff --git a/server/src/services/workspace-runtime-read-model.test.ts b/server/src/services/workspace-runtime-read-model.test.ts index 34c45e7504b..47ef254501f 100644 --- a/server/src/services/workspace-runtime-read-model.test.ts +++ b/server/src/services/workspace-runtime-read-model.test.ts @@ -91,6 +91,45 @@ describe("selectConfiguredRuntimeServiceRows", () => { ]); }); + it("retains unmatched active rows as reconciliation safety evidence", () => { + const configuredWeb = runtimeServiceRow({ + serviceName: "web", + command: "pnpm dev", + }); + const removedActiveWorker = runtimeServiceRow({ + serviceName: "worker", + command: "pnpm worker", + status: "running", + healthStatus: "healthy", + }); + const removedStoppedJob = runtimeServiceRow({ + serviceName: "job", + command: "pnpm job", + status: "stopped", + }); + + const selected = selectConfiguredRuntimeServiceRows( + [removedActiveWorker, removedStoppedJob, configuredWeb], + { services: [{ name: "web", command: "pnpm dev" }] }, + "stopped", + ); + + expect(selected).toEqual([ + expect.objectContaining({ + id: configuredWeb.id, + configIndex: 0, + desiredState: "stopped", + }), + expect.objectContaining({ + id: removedActiveWorker.id, + configIndex: null, + workspaceCommandId: null, + desiredState: null, + status: "running", + }), + ]); + }); + it("matches configured services only to rows with the configured reuse scope", () => { const staleProjectScopedWorker = runtimeServiceRow({ serviceName: "worker", diff --git a/server/src/services/workspace-runtime-read-model.ts b/server/src/services/workspace-runtime-read-model.ts index b6fccd66f76..c89722249ff 100644 --- a/server/src/services/workspace-runtime-read-model.ts +++ b/server/src/services/workspace-runtime-read-model.ts @@ -9,6 +9,11 @@ import { and, desc, eq, inArray } from "drizzle-orm"; type WorkspaceRuntimeServiceRow = typeof workspaceRuntimeServices.$inferSelect; type RuntimeServiceReadDb = Pick; +const ACTIVE_RUNTIME_SERVICE_STATUSES = new Set([ + "provisioning", + "starting", + "running", +]); function runtimeServiceIdentityKey(row: WorkspaceRuntimeServiceRow) { if (row.reuseKey) return row.reuseKey; @@ -77,7 +82,10 @@ export function selectConfiguredRuntimeServiceRows( availableRows.splice(availableRows.indexOf(matchedRow), 1); } - return selectedRows; + return [ + ...selectedRows, + ...availableRows.filter((row) => ACTIVE_RUNTIME_SERVICE_STATUSES.has(row.status)), + ]; } export async function listCurrentRuntimeServicesForProjectWorkspaces(