From 8ff11050fbc7027bf0f262d843efbfde31e06ac6 Mon Sep 17 00:00:00 2001 From: heeyeon Date: Wed, 11 Mar 2026 15:06:05 +0900 Subject: [PATCH 1/4] fix: show running workspace limit alert on worspace update --- site/src/pages/WorkspacePage/Workspace.tsx | 36 ++++++++++++++----- .../WorkspacePage/WorkspaceReadyPage.tsx | 1 + 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 1d6f7c3d6fa96..53bdaa806a425 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -55,6 +55,7 @@ export interface WorkspaceProps { isOwner: boolean; timings?: TypesGen.WorkspaceBuildTimings; startWorkspaceError?: unknown; + updateWorkspaceError?: unknown; } /** @@ -89,6 +90,7 @@ export const Workspace: FC = ({ isOwner, timings, startWorkspaceError, + updateWorkspaceError, }) => { const navigate = useNavigate(); const theme = useTheme(); @@ -125,14 +127,10 @@ export const Workspace: FC = ({ const { shouldShow: shouldShowWorkspaceReadyDelayAlert } = useWorkspaceReadyDelayAlert(timings, workspaceRunning); - const isRunningWorkspaceLimitError = Boolean( - startWorkspaceError && - isApiError(startWorkspaceError) && - startWorkspaceError.response?.status === 409 && - startWorkspaceError.response?.data?.message?.includes( - "Running workspace limit", - ), - ); + const isStartRunningWorkspaceLimitError = + isRunningWorkspaceLimitError(startWorkspaceError); + const isUpdateRunningWorkspaceLimitError = + isRunningWorkspaceLimitError(updateWorkspaceError); return (
= ({ )} - {isRunningWorkspaceLimitError && ( + {isStartRunningWorkspaceLimitError && ( Running workspace limit reached @@ -267,6 +265,18 @@ export const Workspace: FC = ({ )} + {isUpdateRunningWorkspaceLimitError && ( + + Running workspace limit reached + + {getErrorMessage( + updateWorkspaceError, + "Running workspace limit reached (max 1 per user). Stop one or more workspaces to update this workspace.", + )} + + + )} + {workspace.latest_build.job.error && ( Workspace build failed @@ -342,6 +352,14 @@ const countAgents = (resource: TypesGen.WorkspaceResource) => { return resource.agents ? resource.agents.length : 0; }; +const isRunningWorkspaceLimitError = (error: unknown): boolean => + Boolean( + error && + isApiError(error) && + error.response?.status === 409 && + error.response?.data?.message?.includes("Running workspace limit"), + ); + const styles = { content: { padding: 32, diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index f591402240901..86461d86b72dd 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -281,6 +281,7 @@ export const WorkspaceReadyPage: FC = ({ isOwner={isOwner} timings={timingsQuery.data} startWorkspaceError={startWorkspaceMutation.error} + updateWorkspaceError={updateWorkspaceMutation.error} /> Date: Wed, 11 Mar 2026 16:01:02 +0900 Subject: [PATCH 2/4] fix: differentiate running workspace limit alert for update --- coderd/workspacebuilds.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index fa2023dc4bd62..0292a9369384c 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -411,8 +411,12 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { }, nil) if errors.Is(err, errRunningWorkspaceLimitExceeded) { maxRunning := api.DeploymentValues.MaxRunningWorkspacesPerUser.Value() + message := fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to start another.", maxRunning) + if createBuild.TemplateVersionID != uuid.Nil { + message = fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to update this workspace.", maxRunning) + } httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ - Message: fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to start another.", maxRunning), + Message: message, }) return } From dd1744b5a751fb54dbfb04b8634ad526430b579e Mon Sep 17 00:00:00 2001 From: heeyeon Date: Wed, 11 Mar 2026 16:40:56 +0900 Subject: [PATCH 3/4] fix: stop running workspace before applying version update --- site/src/api/api.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 3ecc1564e153d..f690ee2e29b73 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2078,6 +2078,19 @@ class ApiMethods { throw new MissingBuildParameters(missingParameters, activeVersionId); } + // If the workspace is currently running, stop it first so that the + // running workspace limit check on the server passes when we update + // with the new version. + if (workspace.latest_build.status === "running") { + const stopBuild = await this.stopWorkspace(workspace.id); + const awaitedStopBuild = await this.waitForBuild(stopBuild); + if (awaitedStopBuild?.status === "canceled") { + throw new Error( + "Workspace stop was canceled before the update could be applied.", + ); + } + } + return this.postWorkspaceBuild(workspace.id, { transition: "start", template_version_id: activeVersionId, From 297361904caf711dc2dd525cf4452653466d7f1b Mon Sep 17 00:00:00 2001 From: heeyeon Date: Wed, 11 Mar 2026 17:02:12 +0900 Subject: [PATCH 4/4] test: add running workspace limit alert test for update 409 error --- .../WorkspacePage/WorkspacePage.test.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 1c644f981d7a6..fbdbe5e041afd 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -331,6 +331,33 @@ describe("WorkspacePage", () => { }); }); + it("shows a running workspace limit warning when update fails with 409", async () => { + jest + .spyOn(API, "getWorkspaceByOwnerAndName") + .mockResolvedValueOnce(MockOutdatedWorkspace); + jest.spyOn(API, "updateWorkspace").mockRejectedValueOnce({ + isAxiosError: true, + response: { + status: 409, + data: { + message: + "Running workspace limit reached (max 1 per user). Stop one or more workspaces to update this workspace.", + }, + }, + }); + + await renderWorkspacePage(MockWorkspace); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("workspace-update-button")); + const confirmButton = await screen.findByTestId("confirm-button"); + await user.click(confirmButton); + + await screen.findByText( + /Stop one or more workspaces to update this workspace/i, + ); + }); + it("restart the workspace with one time parameters when having the confirmation dialog", async () => { localStorage.removeItem(`${MockUser.id}_ignoredWarnings`); jest.spyOn(API, "getWorkspaceParameters").mockResolvedValue({