From 487867da96d875d4535df671a1c4f5255e053d5f Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Thu, 9 Apr 2026 21:12:07 -0700 Subject: [PATCH 1/3] fix(seer): Decouple create-pr setting from stopping point --- .../seer/overview/utils/seerStoppingPoint.ts | 64 +++++++++++++++---- .../projectDetails/autofixAgent.tsx | 3 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts index 091186a21fd845..7484cb0e856ff1 100644 --- a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts +++ b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts @@ -134,6 +134,14 @@ export function getProjectStoppingPointMutationOptions({ project.slug ); + function resolveStoppingPointValue(stoppingPoint: SelectValue) { + return stoppingPoint === 'root_cause' + ? ('root_cause' as const) + : organization.autoOpenPrs + ? ('open_pr' as const) + : ('code_changes' as const); + } + return mutationOptions({ mutationFn: async ({stoppingPoint}: {stoppingPoint: SelectValue}) => { const tuning = stoppingPoint === 'off' ? ('off' as const) : ('medium' as const); @@ -150,22 +158,10 @@ export function getProjectStoppingPointMutationOptions({ const repositories = preference?.repositories ?? []; const automationHandoff = preference?.automation_handoff; - const stoppingPointValue = - stoppingPoint === 'root_cause' - ? ('root_cause' as const) - : organization.autoOpenPrs - ? ('open_pr' as const) - : ('code_changes' as const); - const preferencePayload = { repositories, - automated_run_stopping_point: stoppingPointValue, - automation_handoff: automationHandoff - ? { - ...automationHandoff, - auto_create_pr: stoppingPointValue === 'open_pr', - } - : automationHandoff, + automated_run_stopping_point: resolveStoppingPointValue(stoppingPoint), + automation_handoff: automationHandoff, }; preferencePromise = fetchMutation({ @@ -177,6 +173,28 @@ export function getProjectStoppingPointMutationOptions({ return await Promise.all([projectPromise, preferencePromise]); }, + onMutate: ({stoppingPoint}: {stoppingPoint: SelectValue}) => { + const previousProject = ProjectsStore.getById(project.id); + const previousPreference = getApiQueryData( + queryClient, + seerPrefsQueryKey + ); + + const tuning = stoppingPoint === 'off' ? ('off' as const) : ('medium' as const); + ProjectsStore.onUpdateSuccess({...project, autofixAutomationTuning: tuning}); + + if (stoppingPoint !== 'off' && previousPreference?.preference) { + setApiQueryData(queryClient, seerPrefsQueryKey, { + ...previousPreference, + preference: { + ...previousPreference.preference, + automated_run_stopping_point: resolveStoppingPointValue(stoppingPoint), + }, + }); + } + + return {previousProject, previousPreference}; + }, onSuccess: ([updatedProject, preferencePayload]) => { ProjectsStore.onUpdateSuccess(updatedProject); @@ -197,6 +215,24 @@ export function getProjectStoppingPointMutationOptions({ } } }, + onError: (_error: unknown, _variables: unknown, context: unknown) => { + const ctx = context as + | { + previousPreference: SeerPreferencesResponse | undefined; + previousProject: Project | undefined; + } + | undefined; + if (ctx?.previousProject) { + ProjectsStore.onUpdateSuccess(ctx.previousProject); + } + if (ctx?.previousPreference) { + setApiQueryData( + queryClient, + seerPrefsQueryKey, + ctx.previousPreference + ); + } + }, onSettled: () => { queryClient.invalidateQueries({queryKey: seerPrefsQueryKey}); diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx index 958b32849a1050..8efef224645598 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx @@ -179,7 +179,8 @@ function StoppingPointField({ stoppingPointMutationOpts.onSuccess?.(data, variables, onMutateResult, context); addSuccessMessage(t('Stopping point updated')); }, - onError: () => { + onError: (error, variables, onMutateResult, context) => { + stoppingPointMutationOpts.onError?.(error, variables, onMutateResult, context); addErrorMessage(t('Failed to update stopping point')); }, }); From 96a6f30a9ff0b8b316aa771ccc8f69308865ae2a Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Fri, 10 Apr 2026 11:27:16 -0700 Subject: [PATCH 2/3] re-order the statements and add a note about setting stopping point to off --- .../seer/overview/utils/seerStoppingPoint.ts | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts index 7484cb0e856ff1..f5085910232dea 100644 --- a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts +++ b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts @@ -152,25 +152,31 @@ export function getProjectStoppingPointMutationOptions({ data: {autofixAutomationTuning: tuning}, }); - let preferencePromise: Promise = - Promise.resolve(undefined); - if (stoppingPoint !== 'off') { - const repositories = preference?.repositories ?? []; - const automationHandoff = preference?.automation_handoff; - - const preferencePayload = { - repositories, - automated_run_stopping_point: resolveStoppingPointValue(stoppingPoint), - automation_handoff: automationHandoff, - }; - - preferencePromise = fetchMutation({ - method: 'POST', - url: `/projects/${organization.slug}/${project.slug}/seer/preferences/`, - data: preferencePayload as unknown as Record, - }); + if (stoppingPoint === 'off') { + // When the stopping pointis set to 'off' we can leave the stoppingPoint + // value as-is because the tuning will take precedence and stop execution + // before we look at the handoff value. + // If we wanted to update the handoff what would we set it to? We can't + // clear it beacuse that would/could change the preferred agent value. + // Therefore we'll skip any update. + return await Promise.all([projectPromise, Promise.resolve(undefined)]); } + const stoppingPointValue = resolveStoppingPointValue(stoppingPoint); + const repositories = preference?.repositories ?? []; + const automationHandoff = preference?.automation_handoff; + + const preferencePayload = { + repositories, + automated_run_stopping_point: stoppingPointValue, + automation_handoff: automationHandoff, + }; + + const preferencePromise = fetchMutation({ + method: 'POST', + url: `/projects/${organization.slug}/${project.slug}/seer/preferences/`, + data: preferencePayload as unknown as Record, + }); return await Promise.all([projectPromise, preferencePromise]); }, onMutate: ({stoppingPoint}: {stoppingPoint: SelectValue}) => { @@ -183,7 +189,7 @@ export function getProjectStoppingPointMutationOptions({ const tuning = stoppingPoint === 'off' ? ('off' as const) : ('medium' as const); ProjectsStore.onUpdateSuccess({...project, autofixAutomationTuning: tuning}); - if (stoppingPoint !== 'off' && previousPreference?.preference) { + if (previousPreference?.preference) { setApiQueryData(queryClient, seerPrefsQueryKey, { ...previousPreference, preference: { From d0b74537c64735bdb6c1b02a2cba4ff77ecdb363 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Fri, 10 Apr 2026 11:27:31 -0700 Subject: [PATCH 3/3] re-order the statements and add a note about setting stopping point to off --- .../views/settings/seer/overview/utils/seerStoppingPoint.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts index f5085910232dea..bbffba4dabcaa2 100644 --- a/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts +++ b/static/app/views/settings/seer/overview/utils/seerStoppingPoint.ts @@ -189,7 +189,11 @@ export function getProjectStoppingPointMutationOptions({ const tuning = stoppingPoint === 'off' ? ('off' as const) : ('medium' as const); ProjectsStore.onUpdateSuccess({...project, autofixAutomationTuning: tuning}); - if (previousPreference?.preference) { + if (stoppingPoint !== 'off' && previousPreference?.preference) { + // If stopping point is 'off' then we're not updating the handoff value. + // Instead we'll just leave it (and the preferred agent value) as-is. + // The tuning value will take precedence and stop execution before we + // look at the handoff value. setApiQueryData(queryClient, seerPrefsQueryKey, { ...previousPreference, preference: {