Workspace update fails for templates that use dynamic parameters
- Dominant language
- TypeScript
- Stars
- 130
- Forks
- 48
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 15
Description
## Symptom
Updating a workspace from VS Code fails with a generic "resolve parameters" 400 error when the template uses dynamic parameters (the `dynamic-parameters` experiment, [docs](https://coder.com/docs/admin/templates/extending-templates/dynamic-parameters)) and any of the following are true:
- A parameter is conditionally `required` based on another input value.
- A parameter only appears for certain combinations of other inputs (the static rich-parameters list does not include it).
- A parameter's options are computed dynamically (the static `options` list is empty or stale).
The user sees the failure as a build error in the workspace logs, with no clear indication that the workspace update endpoint expected additional or different parameter values than were provided.
## Root cause
`collectUpdateParameters` ([src/api/updateParameters.ts](https://github.com/coder/vscode-coder/blob/main/src/api/updateParameters.ts)) calls `getTemplateVersionRichParameters`, which returns the *static* parameter list captured at template-version provisioning time ([coderd/templateversions.go:287](https://github.com/coder/coder/blob/main/coderd/templateversions.go#L287)). It then mirrors the dashboard's legacy-params `getMissingParameters` check.
For templates using dynamic parameters, the dashboard takes a different path entirely:
- It opens a WebSocket at `/api/v2/templateversions/{id}/dynamic-parameters?user_id=…` ([site/src/api/api.ts:1175](https://github.com/coder/coder/blob/main/site/src/api/api.ts#L1175)).
- The server reactively re-renders the parameter form as the user types, evaluating `coder_parameter` conditionals against owner data, previous build values, and presets.
- On submit, the build endpoint calls `wsbuilder.getDynamicParameters` → `dynamicparameters.ResolveParameters` ([coderd/wsbuilder/wsbuilder.go:881](https://github.com/coder/coder/blob/main/coderd/wsbuilder/wsbuilder.go#L881)), which returns an `hcl.Diagnostics` error if any reactive constraint is unsatisfied.
The extension never opens that socket, so its prompt set is whatever the static list said at provisioning time. When the dynamic resolver evaluates the submitted build values and finds a conditionally-required parameter missing (or a dynamically-restricted option violated), the build fails.
## Reproduction
1. Author a template with `terraform-provider-coder v2.x` declaring two parameters where the second is `optional` only when the first has a specific value (a typical reactive form), and enable the `dynamic-parameters` experiment on the deployment.
2. Create a workspace from that template, choosing values such that the conditional path requires the second parameter.
3. Update the template's active version (any change that keeps both parameters but does not alter the conditional logic).
4. From VS Code, run `coder.updateWorkspace` on the workspace.
5. Observe the build fails with a 400 "resolve parameters" error from `wsbuilder`. The extension shows a generic workspace-update-failed notification; the underlying diagnostic is only visible in the workspace logs.
## Scope of impact
- **Affected:** any deployment with the `dynamic-parameters` experiment enabled (the path the Coder team is pushing as the default going forward).
- **Not affected:** deployments still on legacy/classic parameters — those continue to work with the existing `needsPrompt` logic.
- **Partial impact:** templates that *enable* dynamic parameters but never use any reactive constructs (no conditional `required`, no dynamic option lists, no conditionally-appearing parameters) update fine, because the static list happens to be accurate.
## Proposed mitigations
In rough order of effort:
1. **Detect and warn (S):** read the template's `use_classic_parameter_flow` (or equivalent) flag in `collectUpdateParameters`. If false, surface a one-line warning telling the user to update via the dashboard (with a deep link) and skip the prompts. Currently we silently send incomplete data.
2. **One-shot REST evaluate (M):** call `POST /api/v2/templateversions/{id}/dynamic-parameters/evaluate` ([coderd/parameters.go](https://github.com/coder/coder/blob/main/coderd/parameters.go), [api.ts:1155](https://github.com/coder/coder/blob/main/site/src/api/api.ts#L1155)) with the current build parameters as `inputs`, feed the returned non-reactive parameter list into the existing `promptSpec` + QuickPick flow. Loses interactive reactivity but picks up server-evaluated `required` and option lists. Best ROI for most templates.
3. **WebSocket + Webview (L):** open `/api/v2/templateversions/{id}/dynamic-parameters` from the extension, render a reactive form in a Webview, send each answer back as the user fills it in. Closest to the dashboard UX but a real feature, not a fix. Existing `reconnectingWebSocket.ts` infra is reusable.
## Related
- PR #967 (this PR): legacy-params drift detection and immutable-param prompting. Closes the legacy gap but explicitly does not address dynamic params; the JSDoc on `promptSpec` calls this out.
- The dashboard skips its `getMissingParameters` client-side check when `isDynamicParametersEnabled` is true ([site/src/api/api.ts:2532](https://github.com/coder/coder/blob/main/site/src/api/api.ts#L2532)) and relies entirely on the server resolver.
Contributor guide
Assessment
This issue has not been assessed yet.