From 57c3c075932e025ee9358c29f24c319cdde37343 Mon Sep 17 00:00:00 2001 From: pedrohsdb Date: Wed, 28 Jan 2026 12:05:17 -0800 Subject: [PATCH] Fix script generation race condition causing hardcoded parameter values (#SKY-7653) (#4570) --- .../generate_workflow_parameters.py | 16 ++++++++ skyvern/forge/sdk/core/skyvern_context.py | 4 ++ skyvern/forge/sdk/workflow/service.py | 39 +++++++++++++++++++ skyvern/services/task_v2_service.py | 10 +++-- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/skyvern/core/script_generations/generate_workflow_parameters.py b/skyvern/core/script_generations/generate_workflow_parameters.py index 70362174..7207fe67 100644 --- a/skyvern/core/script_generations/generate_workflow_parameters.py +++ b/skyvern/core/script_generations/generate_workflow_parameters.py @@ -8,6 +8,7 @@ import structlog from pydantic import BaseModel from skyvern.forge import app +from skyvern.forge.sdk.core import skyvern_context from skyvern.forge.sdk.prompting import PromptEngine from skyvern.webeye.actions.actions import ActionType @@ -64,6 +65,21 @@ async def generate_workflow_parameters_schema( elif action_type == ActionType.SELECT_OPTION: value = action.get("option", "") + # Skip actions without data - addresses race condition where script generation + # runs before action data is fully saved to database (SKY-7653) + if not value: + LOG.debug( + "Skipping action without data during field mapping generation", + action_type=action_type, + action_id=action.get("action_id", ""), + task_id=task_id, + ) + # Mark that we had incomplete actions - triggers finalize regeneration + ctx = skyvern_context.current() + if ctx: + ctx.script_gen_had_incomplete_actions = True + continue + # Check if this action has an existing field name that must be preserved existing_field_name = existing_field_assignments.get(action_counter) diff --git a/skyvern/forge/sdk/core/skyvern_context.py b/skyvern/forge/sdk/core/skyvern_context.py index 40b7d00a..5c998e6a 100644 --- a/skyvern/forge/sdk/core/skyvern_context.py +++ b/skyvern/forge/sdk/core/skyvern_context.py @@ -75,6 +75,10 @@ class SkyvernContext: action_ai_overrides: dict[str, dict[int, str]] = field(default_factory=dict) action_counters: dict[str, int] = field(default_factory=dict) + # Track if script generation skipped any actions due to missing data (race condition) + # Used to determine if finalize regeneration is needed at workflow completion + script_gen_had_incomplete_actions: bool = False + def __repr__(self) -> str: return f"SkyvernContext(request_id={self.request_id}, organization_id={self.organization_id}, task_id={self.task_id}, step_id={self.step_id}, workflow_id={self.workflow_id}, workflow_run_id={self.workflow_run_id}, task_v2_id={self.task_v2_id}, max_steps_override={self.max_steps_override}, run_id={self.run_id})" diff --git a/skyvern/forge/sdk/workflow/service.py b/skyvern/forge/sdk/workflow/service.py index 6ea44194..2185670f 100644 --- a/skyvern/forge/sdk/workflow/service.py +++ b/skyvern/forge/sdk/workflow/service.py @@ -849,6 +849,7 @@ class WorkflowService: workflow_run=workflow_run, block_labels=block_labels, blocks_to_update=blocks_to_update, + finalize=True, # Force regeneration to ensure field mappings have complete action data ) # Execute finally block if configured. Skip for: canceled (user explicitly stopped) @@ -3352,10 +3353,48 @@ class WorkflowService: workflow_run: WorkflowRun, block_labels: list[str] | None = None, blocks_to_update: set[str] | None = None, + finalize: bool = False, ) -> None: + """ + Generate or regenerate workflow script if needed. + + Args: + workflow: The workflow definition + workflow_run: The workflow run instance + block_labels: Optional list of specific block labels to generate + blocks_to_update: Set of block labels that need regeneration + finalize: If True, check if any actions were skipped during script generation + due to missing data (race condition). Only regenerate if needed. + This fixes SKY-7653 while avoiding unnecessary regeneration costs. + """ code_gen = workflow_run.code_gen blocks_to_update = set(blocks_to_update or []) + # When finalizing, only regenerate if script generation had incomplete actions. + # This addresses the race condition (SKY-7653) while avoiding unnecessary + # regeneration costs when the script is already complete. + if finalize: + current_context = skyvern_context.current() + if current_context and current_context.script_gen_had_incomplete_actions: + LOG.info( + "Finalize: regenerating script due to incomplete actions during generation", + workflow_run_id=workflow_run.workflow_run_id, + ) + task_block_labels = { + block.label + for block in workflow.workflow_definition.blocks + if block.label and block.block_type in BLOCK_TYPES_THAT_SHOULD_BE_CACHED + } + blocks_to_update.update(task_block_labels) + blocks_to_update.add(settings.WORKFLOW_START_BLOCK_LABEL) + # Reset flag after triggering regeneration to prevent stale state + current_context.script_gen_had_incomplete_actions = False + else: + LOG.debug( + "Finalize: skipping regeneration - no incomplete actions detected", + workflow_run_id=workflow_run.workflow_run_id, + ) + LOG.info( "Generate script?", block_labels=block_labels, diff --git a/skyvern/services/task_v2_service.py b/skyvern/services/task_v2_service.py index af844af3..4759d341 100644 --- a/skyvern/services/task_v2_service.py +++ b/skyvern/services/task_v2_service.py @@ -967,10 +967,12 @@ async def run_task_v2_helper( context=context, screenshots=completion_screenshots, ) - await app.WORKFLOW_SERVICE.generate_script_if_needed( - workflow=workflow, - workflow_run=workflow_run, - ) + if task_v2.run_with == "code": + await app.WORKFLOW_SERVICE.generate_script_if_needed( + workflow=workflow, + workflow_run=workflow_run, + finalize=True, # Force regeneration to ensure field mappings have complete action data + ) break # total step number validation