From f723f548795a419609d8eb89add4d8f83a8d0211 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Thu, 4 Jun 2026 15:56:47 +0200 Subject: [PATCH] fix(editor): Persist switching workflow credential resolver back to system resolver (#31703) Co-authored-by: Claude Opus 4.8 (1M context) --- .../WorkflowSettings/WorkflowSettings.test.ts | 34 +++++++++++++++++++ .../WorkflowSettings/WorkflowSettings.vue | 8 +++++ 2 files changed, 42 insertions(+) diff --git a/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.test.ts b/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.test.ts index 434ba870d83..e76c95058d4 100644 --- a/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.test.ts +++ b/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.test.ts @@ -998,6 +998,40 @@ describe('WorkflowSettingsVue', () => { ); }); + it('should save with empty credentialResolverId when switching back to the system resolver', async () => { + workflowDocumentStore.setSettings({ credentialResolverId: 'resolver-1' }); + + const { getByTestId, getByRole } = createComponent({ pinia }); + await flushPromises(); + + await waitFor(() => { + expect(restApiClient.getCredentialResolvers).toHaveBeenCalled(); + }); + + // Open the dropdown and pick the n8n system resolver + const resolverContainer = getByTestId('workflow-settings-credential-resolver'); + await userEvent.click(within(resolverContainer).getByRole('combobox')); + + await waitFor(async () => { + const options = within(document.body as HTMLElement).getAllByRole('option'); + const systemResolver = options.find((o) => o.textContent?.includes('N8n Resolver')); + expect(systemResolver).toBeTruthy(); + await userEvent.click(systemResolver!); + }); + await flushPromises(); + + await userEvent.click(getByRole('button', { name: 'Save' })); + + // `undefined` would be stripped during serialization and the merge on the backend + // would keep the old id, so the clear must be sent as an explicit empty string. + expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith( + '1', + expect.objectContaining({ + settings: expect.objectContaining({ credentialResolverId: '' }), + }), + ); + }); + it('should disable credential resolver dropdown when environment is read-only', async () => { sourceControlStore.preferences.branchReadOnly = true; diff --git a/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.vue b/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.vue index 45800932442..d0e818fc538 100644 --- a/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.vue +++ b/packages/frontend/editor-ui/src/app/components/WorkflowSettings/WorkflowSettings.vue @@ -712,6 +712,14 @@ const saveSettings = async () => { } delete data.settings.maxExecutionTimeout; + // `credentialResolverId` is `undefined` in-memory when the n8n system resolver is + // selected. The backend merges settings for partial updates, so an absent key keeps + // the previously-saved id. Send an explicit empty string so the merge clears it + // (the backend drops the falsy value before persisting). + if (isCredentialResolverEnabled.value && !data.settings.credentialResolverId) { + data.settings.credentialResolverId = ''; + } + isLoading.value = true; data.versionId = workflowDocumentStore.value.versionId; data.expectedChecksum = workflowDocumentStore.value.checksum;