From e2cbf03f853256361221dfa972be85b78e986f2d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 20 Feb 2026 14:21:33 -0600 Subject: [PATCH] fix: ensure stopping a workspace before starting it when updating (#22201) Dynamic parameters were not following the same code path as legacy Closes https://github.com/coder/coder/issues/20333 --- site/src/api/api.ts | 77 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 48ab1749ad..86b9a3de62 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2389,44 +2389,23 @@ class ApiMethods { const activeVersionId = template.active_version_id; - if (isDynamicParametersEnabled) { - try { - return await this.postWorkspaceBuild(workspace.id, { - transition: "start", - template_version_id: activeVersionId, - rich_parameter_values: newBuildParameters, - }); - } catch (error) { - // If the build failed because of a parameter validation error, then we - // throw a special sentinel error that can be caught by the caller. - if ( - isApiError(error) && - error.response.status === 400 && - error.response.data.validations && - error.response.data.validations.length > 0 - ) { - throw new ParameterValidationError( - activeVersionId, - error.response.data.validations, - ); - } - throw error; + if (!isDynamicParametersEnabled) { + // Dynamic templates rely on the backend to fully validate parameters. + // Legacy templates do not, so do an additional check for any missing params. + const templateParameters = + await this.getTemplateVersionRichParameters(activeVersionId); + + const missingParameters = getMissingParameters( + oldBuildParameters, + newBuildParameters, + templateParameters, + ); + + if (missingParameters.length > 0) { + throw new MissingBuildParameters(missingParameters, activeVersionId); } } - const templateParameters = - await this.getTemplateVersionRichParameters(activeVersionId); - - const missingParameters = getMissingParameters( - oldBuildParameters, - newBuildParameters, - templateParameters, - ); - - if (missingParameters.length > 0) { - throw new MissingBuildParameters(missingParameters, activeVersionId); - } - // Stop the workspace if it is already running. if (workspace.latest_build.status === "running") { const stopBuild = await this.stopWorkspace(workspace.id); @@ -2440,11 +2419,29 @@ class ApiMethods { } } - return this.postWorkspaceBuild(workspace.id, { - transition: "start", - template_version_id: activeVersionId, - rich_parameter_values: newBuildParameters, - }); + try { + return await this.postWorkspaceBuild(workspace.id, { + transition: "start", + template_version_id: activeVersionId, + rich_parameter_values: newBuildParameters, + }); + } catch (error) { + // If the build failed because of a parameter validation error, then we + // throw a special sentinel error that can be caught by the caller. + if ( + isDynamicParametersEnabled && + isApiError(error) && + error.response.status === 400 && + error.response.data.validations && + error.response.data.validations.length > 0 + ) { + throw new ParameterValidationError( + activeVersionId, + error.response.data.validations, + ); + } + throw error; + } }; getWorkspaceResolveAutostart = async (