From 5ff503b8fcffc97056337bc981a8375f0a838fe0 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Wed, 24 Sep 2025 13:23:31 -0300 Subject: [PATCH] fix: force task to be created with latest version (#19923) Fixes https://github.com/coder/coder/issues/19744 --- site/src/pages/TasksPage/TaskPrompt.tsx | 27 ++++++++++++++++--- .../src/pages/TasksPage/TasksPage.stories.tsx | 17 ++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/site/src/pages/TasksPage/TaskPrompt.tsx b/site/src/pages/TasksPage/TaskPrompt.tsx index cebbe2d2ea..ad08647ba5 100644 --- a/site/src/pages/TasksPage/TaskPrompt.tsx +++ b/site/src/pages/TasksPage/TaskPrompt.tsx @@ -186,11 +186,12 @@ const CreateTaskForm: FC = ({ templates, onSuccess }) => { const createTaskMutation = useMutation({ mutationFn: async ({ prompt }: CreateTaskMutationFnProps) => - API.experimental.createTask(user.id, { + createTaskWithLatestTemplateVersion( prompt, - template_version_id: selectedTemplate.active_version_id, - template_version_preset_id: selectedPresetId, - }), + user.id, + selectedTemplate.id, + selectedPresetId, + ), onSuccess: async (task) => { await queryClient.invalidateQueries({ queryKey: ["tasks"] }); onSuccess(task); @@ -427,3 +428,21 @@ function sortByDefault(a: Preset, b: Preset) { // Otherwise, sort alphabetically by name return a.Name.localeCompare(b.Name); } + +// TODO: Enforce task creation to always use the latest active template version. +// During task creation, the active version might change between template load +// and user action. Since handling this in the FE cannot guarantee correctness, +// we should move the logic to the BE after the experimental phase. +async function createTaskWithLatestTemplateVersion( + prompt: string, + userId: string, + templateId: string, + presetId: string | undefined, +): Promise { + const template = await API.getTemplate(templateId); + return API.experimental.createTask(userId, { + prompt, + template_version_id: template.active_version_id, + template_version_preset_id: presetId, + }); +} diff --git a/site/src/pages/TasksPage/TasksPage.stories.tsx b/site/src/pages/TasksPage/TasksPage.stories.tsx index dfcb081017..84cba0be64 100644 --- a/site/src/pages/TasksPage/TasksPage.stories.tsx +++ b/site/src/pages/TasksPage/TasksPage.stories.tsx @@ -245,7 +245,12 @@ export const CreateTaskSuccessfully: Story = { }), }, beforeEach: () => { + const activeVersionId = `${MockTemplate.active_version_id}-latest`; spyOn(API, "getTemplates").mockResolvedValue([MockTemplate]); + spyOn(API, "getTemplate").mockResolvedValue({ + ...MockTemplate, + active_version_id: activeVersionId, + }); spyOn(API.experimental, "getTasks") .mockResolvedValueOnce(MockTasks) .mockResolvedValue([MockNewTaskData, ...MockTasks]); @@ -262,6 +267,17 @@ export const CreateTaskSuccessfully: Story = { await userEvent.click(submitButton); }); + await step("Uses latest template version", () => { + expect(API.experimental.createTask).toHaveBeenCalledWith( + MockUserOwner.id, + { + prompt: MockNewTaskData.prompt, + template_version_id: `${MockTemplate.active_version_id}-latest`, + template_version_preset_id: undefined, + }, + ); + }); + await step("Displays success message", async () => { const body = within(canvasElement.ownerDocument.body); const successMessage = await body.findByText(/task created/i); @@ -281,6 +297,7 @@ export const CreateTaskError: Story = { decorators: [withGlobalSnackbar], beforeEach: () => { spyOn(API, "getTemplates").mockResolvedValue([MockTemplate]); + spyOn(API, "getTemplate").mockResolvedValue(MockTemplate); spyOn(API.experimental, "getTasks").mockResolvedValue(MockTasks); spyOn(API.experimental, "createTask").mockRejectedValue( mockApiError({