From a664ef50d570d593573c85947b23311ebbe02f05 Mon Sep 17 00:00:00 2001 From: Suchintan Date: Thu, 5 Feb 2026 07:40:44 -0500 Subject: [PATCH] Revert "Remove code generation deletion confirmation prompt" (#4639) Co-authored-by: Suchintan Singh --- .../src/routes/workflows/editor/Workspace.tsx | 36 ++++++++++++ .../src/store/WorkflowHasChangesStore.ts | 34 ++++++++++- skyvern/exceptions.py | 5 ++ skyvern/forge/sdk/routes/agent_protocol.py | 8 +++ skyvern/forge/sdk/workflow/service.py | 58 ++++++++++++++++++- 5 files changed, 138 insertions(+), 3 deletions(-) diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 007b031f..a86e9a64 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -356,6 +356,8 @@ function Workspace({ await saveWorkflow.mutateAsync(); + workflowChangesStore.setSaidOkToCodeCacheDeletion(false); + queryClient.invalidateQueries({ queryKey: ["cache-key-values", workflowPermanentId, cacheKey], }); @@ -1111,6 +1113,40 @@ function Workspace({ + {/* confirm code cache deletion dialog */} + { + !open && workflowChangesStore.setShowConfirmCodeCacheDeletion(false); + !open && workflowChangesStore.setSaidOkToCodeCacheDeletion(false); + }} + > + + + Are you sure? + + Saving will delete cached code, and Skyvern will re-generate it in + the next run. Proceed? + + + + + + + + + + + {/* cache key value delete dialog */} SaveData | null; hasChanges: boolean; saveIsPending: boolean; + saidOkToCodeCacheDeletion: boolean; + showConfirmCodeCacheDeletion: boolean; isInternalUpdate: boolean; setGetSaveData: (getSaveData: () => SaveData) => void; setHasChanges: (hasChanges: boolean) => void; setSaveIsPending: (isPending: boolean) => void; + setSaidOkToCodeCacheDeletion: (saidOkToCodeCacheDeletion: boolean) => void; + setShowConfirmCodeCacheDeletion: (show: boolean) => void; setIsInternalUpdate: (isInternalUpdate: boolean) => void; }; @@ -44,6 +48,8 @@ const useWorkflowHasChangesStore = create((set) => { return { hasChanges: false, saveIsPending: false, + saidOkToCodeCacheDeletion: false, + showConfirmCodeCacheDeletion: false, isInternalUpdate: false, getSaveData: () => null, setGetSaveData: (getSaveData: () => SaveData) => { @@ -55,6 +61,12 @@ const useWorkflowHasChangesStore = create((set) => { setSaveIsPending: (isPending: boolean) => { set({ saveIsPending: isPending }); }, + setSaidOkToCodeCacheDeletion: (saidOkToCodeCacheDeletion: boolean) => { + set({ saidOkToCodeCacheDeletion }); + }, + setShowConfirmCodeCacheDeletion: (show: boolean) => { + set({ showConfirmCodeCacheDeletion: show }); + }, setIsInternalUpdate: (isInternalUpdate: boolean) => { set({ isInternalUpdate }); }, @@ -64,8 +76,13 @@ const useWorkflowHasChangesStore = create((set) => { const useWorkflowSave = (opts?: WorkflowSaveOpts) => { const credentialGetter = useCredentialGetter(); const queryClient = useQueryClient(); - const { getSaveData, setHasChanges, setSaveIsPending } = - useWorkflowHasChangesStore(); + const { + getSaveData, + saidOkToCodeCacheDeletion, + setHasChanges, + setSaveIsPending, + setShowConfirmCodeCacheDeletion, + } = useWorkflowHasChangesStore(); const saveWorkflowMutation = useMutation({ mutationFn: async () => { @@ -149,6 +166,11 @@ const useWorkflowSave = (opts?: WorkflowSaveOpts) => { headers: { "Content-Type": "text/plain", }, + params: { + delete_code_cache_is_ok: saidOkToCodeCacheDeletion + ? "true" + : "false", + }, }, ); }, @@ -182,6 +204,14 @@ const useWorkflowSave = (opts?: WorkflowSaveOpts) => { onError: (error: AxiosError) => { const detail = (error.response?.data as { detail?: string })?.detail; + if ( + detail && + detail.startsWith("No confirmation for code cache deletion") + ) { + setShowConfirmCodeCacheDeletion(true); + return; + } + toast({ title: "Error", description: detail ? detail : error.message, diff --git a/skyvern/exceptions.py b/skyvern/exceptions.py index 38ef49e6..c20184e6 100644 --- a/skyvern/exceptions.py +++ b/skyvern/exceptions.py @@ -837,6 +837,11 @@ class BrowserProfileNotFound(SkyvernHTTPException): super().__init__(message, status_code=status.HTTP_404_NOT_FOUND) +class CannotUpdateWorkflowDueToCodeCache(SkyvernException): + def __init__(self, workflow_permanent_id: str) -> None: + super().__init__(f"No confirmation for code cache deletion on {workflow_permanent_id}.") + + class APIKeyNotFound(SkyvernHTTPException): def __init__(self, organization_id: str) -> None: super().__init__(f"No valid API key token found for organization {organization_id}") diff --git a/skyvern/forge/sdk/routes/agent_protocol.py b/skyvern/forge/sdk/routes/agent_protocol.py index b66626c7..9c9068b3 100644 --- a/skyvern/forge/sdk/routes/agent_protocol.py +++ b/skyvern/forge/sdk/routes/agent_protocol.py @@ -25,6 +25,7 @@ from skyvern import analytics from skyvern._version import __version__ from skyvern.config import settings from skyvern.exceptions import ( + CannotUpdateWorkflowDueToCodeCache, MissingBrowserAddressError, SkyvernHTTPException, ) @@ -910,6 +911,7 @@ async def update_workflow_legacy( ..., description="The ID of the workflow to update. Workflow ID starts with `wpid_`.", examples=["wpid_123"] ), current_org: Organization = Depends(org_auth_service.get_current_org), + delete_code_cache_is_ok: bool = Query(False), ) -> Workflow: analytics.capture("skyvern-oss-agent-workflow-update") # validate the workflow @@ -925,7 +927,13 @@ async def update_workflow_legacy( organization=current_org, request=workflow_create_request, workflow_permanent_id=workflow_id, + delete_code_cache_is_ok=delete_code_cache_is_ok, ) + except CannotUpdateWorkflowDueToCodeCache as e: + raise HTTPException( + status_code=422, + detail=str(e), + ) from e except WorkflowDefinitionValidationException as e: raise e except (SkyvernHTTPException, ValidationError) as e: diff --git a/skyvern/forge/sdk/workflow/service.py b/skyvern/forge/sdk/workflow/service.py index a75315c6..52054f10 100644 --- a/skyvern/forge/sdk/workflow/service.py +++ b/skyvern/forge/sdk/workflow/service.py @@ -35,6 +35,7 @@ from skyvern.exceptions import ( BlockNotFound, BrowserProfileNotFound, BrowserSessionNotFound, + CannotUpdateWorkflowDueToCodeCache, FailedToSendWebhook, InvalidCredentialId, MissingValueForParameter, @@ -1957,6 +1958,7 @@ class WorkflowService: workflow_definition: WorkflowDefinition, organization_id: str, delete_script: bool = True, + delete_code_cache_is_ok: bool = False, ) -> None: if workflow_definition: workflow_definition.validate() @@ -2032,6 +2034,27 @@ class WorkflowService: ) return + if published_groups and not delete_code_cache_is_ok: + LOG.info( + "Workflow definition changed, asking user if clearing published cached blocks is ok", + workflow_id=workflow.workflow_id, + workflow_permanent_id=previous_valid_workflow.workflow_permanent_id, + organization_id=organization_id, + previous_version=previous_valid_workflow.version, + new_version=workflow.version, + invalidate_reason=plan.reason, + invalidate_label=plan.label, + invalidate_index_prev=plan.previous_index, + invalidate_index_new=plan.new_index, + block_labels_to_disable=plan.block_labels_to_disable, + to_clear_published_cnt=len(published_groups), + to_clear_non_published_cnt=len(cached_groups), + ) + + raise CannotUpdateWorkflowDueToCodeCache( + workflow_permanent_id=previous_valid_workflow.workflow_permanent_id, + ) + try: groups_to_clear = [*cached_groups, *published_groups] await self._clear_cached_block_groups( @@ -2073,7 +2096,38 @@ class WorkflowService: ) return - to_delete = candidates + to_delete_published = [script for script in candidates if script.status == ScriptStatus.published] + to_delete = [script for script in candidates if script.status != ScriptStatus.published] + + if len(to_delete_published) > 0: + if not delete_code_cache_is_ok: + LOG.info( + "Workflow definition changed, asking user if deleting published code is ok", + workflow_id=workflow.workflow_id, + workflow_permanent_id=previous_valid_workflow.workflow_permanent_id, + organization_id=organization_id, + previous_version=previous_valid_workflow.version, + new_version=workflow.version, + to_delete_non_published_cnt=len(to_delete), + to_delete_published_cnt=len(to_delete_published), + ) + + raise CannotUpdateWorkflowDueToCodeCache( + workflow_permanent_id=previous_valid_workflow.workflow_permanent_id, + ) + else: + LOG.info( + "Workflow definition changed, user answered yes to deleting published code", + workflow_id=workflow.workflow_id, + workflow_permanent_id=previous_valid_workflow.workflow_permanent_id, + organization_id=organization_id, + previous_version=previous_valid_workflow.version, + new_version=workflow.version, + to_delete_non_published_cnt=len(to_delete), + to_delete_published_cnt=len(to_delete_published), + ) + + to_delete.extend(to_delete_published) if len(to_delete) > 0: try: @@ -3094,6 +3148,7 @@ class WorkflowService: request: WorkflowCreateYAMLRequest, workflow_permanent_id: str | None = None, delete_script: bool = True, + delete_code_cache_is_ok: bool = True, ) -> Workflow: organization_id = organization.organization_id LOG.info( @@ -3187,6 +3242,7 @@ class WorkflowService: workflow_definition=workflow_definition, organization_id=organization_id, delete_script=delete_script, + delete_code_cache_is_ok=delete_code_cache_is_ok, ) return updated_workflow