Skip to content

fix: enforce workflow validation before deploy and run#3505

Open
elliotllliu wants to merge 1 commit intosimstudioai:mainfrom
elliotllliu:fix/validation-bypass
Open

fix: enforce workflow validation before deploy and run#3505
elliotllliu wants to merge 1 commit intosimstudioai:mainfrom
elliotllliu:fix/validation-bypass

Conversation

@elliotllliu
Copy link

Summary

Fixes #3444 — Broken/unconfigured workflows can no longer be deployed or run.

Problem

  1. panel.tsx had hasValidationErrors hardcoded to false, so Deploy/Run buttons were never disabled
  2. The deploy API (/api/workflows/[id]/deploy) only validated schedule data, never checking if blocks were properly configured or connected

Solution

New validation module (lib/workflows/validation.ts):

  • validateWorkflowBlocks(blocks, edges) checks:
    • All required sub-block fields are filled (supports conditional required configs)
    • Non-trigger blocks have at least one incoming connection
    • Skips disabled blocks and container nodes (loops, parallels)

Frontend (panel.tsx):

  • Replace hardcoded false with live validation via useMemo + validateWorkflowBlocks
  • Deploy/Run buttons disabled when errors exist

Backend (deploy/route.ts):

  • Call validateWorkflowBlocks() after loading workflow state
  • Return 400 with detailed error messages listing each validation failure

Changes

File Change
apps/sim/lib/workflows/validation.ts New validation utility (99 lines)
apps/sim/app/workspace/.../panel.tsx Use real validation instead of hardcoded false
apps/sim/app/api/workflows/[id]/deploy/route.ts Add block validation before deploy

Test plan

  1. Create a new workflow, add blocks without configuring required fields
  2. Verify Deploy/Run buttons are disabled
  3. Try deploying via API directly → expect 400 with validation errors
  4. Configure all blocks properly → buttons become enabled, deploy succeeds

Previously, hasValidationErrors was hardcoded to false in panel.tsx,
and the deploy API only validated schedule data. This allowed users to
deploy completely broken/unconfigured workflows.

Frontend (panel.tsx):
- Replace hardcoded `false` with actual validation using
  validateWorkflowBlocks() that checks required sub-block fields
  and block connectivity
- Deploy/Run buttons are now disabled when validation errors exist

Backend (deploy/route.ts):
- Call validateWorkflowBlocks() before deploying
- Return 400 with detailed error messages when validation fails

New file (lib/workflows/validation.ts):
- validateWorkflowBlocks(): checks that all required sub-block fields
  are filled and non-trigger blocks have incoming connections
- Supports conditional required fields (field-dependent validation)
- Skips disabled blocks and container nodes (loops, parallels)

Fixes simstudioai#3444
@cursor
Copy link

cursor bot commented Mar 10, 2026

PR Summary

Medium Risk
Adds new workflow validation that can block running/deploying when required fields or connections are missing; incorrect validation rules could prevent legitimate workflows from executing or deploying.

Overview
Prevents broken workflows from running or deploying by introducing shared block/graph validation.

A new lib/workflows/validation.ts validates enabled blocks for missing required sub-block values (including conditional required rules) and flags non-trigger blocks without incoming edges (skipping loop/parallel containers). The workflow panel now computes hasValidationErrors from this validator to disable Run when invalid, and the deploy API (/api/workflows/[id]/deploy) now rejects invalid workflows with a 400 including aggregated per-block error messages before schedule validation.

Written by Cursor Bugbot for commit 9f05a1f. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 10, 2026 9:07am

Request Review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

blockName: blockState.name,
message: 'Block is not connected to any input',
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocks inside containers always fail connection validation

High Severity

The "incoming connection" check doesn't account for blocks that are children of loop/parallel containers. Blocks inside a container have blockState.data?.parentId set, but the first block inside any container won't have an incoming edge — it's the loop/parallel entry point. The validation incorrectly flags these as "not connected to any input," producing false errors that prevent deploying or running any workflow using loops or parallels. Blocks with a parentId need to be excluded from the incoming-edge requirement.

Additional Locations (1)

Fix in Cursor Fix in Web

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR fixes a long-standing gap where broken or unconfigured workflows could still be deployed and run: it introduces a shared validateWorkflowBlocks utility, wires it into the Run button in panel.tsx, and adds a server-side validation gate in the deploy API route.

Key changes:

  • lib/workflows/validation.ts — new utility that checks required sub-block fields and ensures non-trigger blocks have at least one incoming edge
  • panel.tsx — replaces hardcoded false with a live useMemo-backed validation result; only the Run button is gated
  • deploy/route.ts — validates blocks after loading workflow state; returns 400 with a detailed message listing every failing block

Issues found:

  • The Deploy button (deploy.tsx) is not gated on hasValidationErrors; the PR description claims both buttons are disabled, but only Run is. The backend 400 acts as a fallback, but the frontend UX is inconsistent.
  • isSubBlockRequired does not handle the function variant of SubBlockConfig.required (i.e. required: (values) => { field, value }). Any block that uses that form will have every occurrence treated as always-required, producing false-positive validation errors and blocking deployment of valid workflows.
  • The @/lib/workflows/validation import in panel.tsx is placed after the @/stores/… group, violating the project's import-ordering convention.

Confidence Score: 3/5

  • Safe to merge with caveats — the backend guard prevents broken deploys, but the Deploy button UX gap and the unhandled function-form required should be addressed before shipping.
  • Backend validation correctly blocks broken deploys (safety net is solid). However, the Deploy button remains enabled for invalid workflows in the UI (inconsistent with the stated goal), and the function-variant of required is silently mis-handled, which could cause false-positive blocking once any block adopts that pattern.
  • apps/sim/lib/workflows/validation.ts (function-variant bug) and apps/sim/app/workspace/.../deploy/deploy.tsx (missing validation gate)

Important Files Changed

Filename Overview
apps/sim/lib/workflows/validation.ts New validation utility; correctly handles boolean and object required variants, but silently mis-handles the function variant of required (treating every field as always-required), and may produce multiple errors for the same block.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx Run button now correctly gates on live validation errors via useMemo, but the sibling Deploy button component receives no hasValidationErrors prop and remains un-gated in the frontend.
apps/sim/app/api/workflows/[id]/deploy/route.ts Backend correctly validates blocks before deploying and returns a 400 with descriptive errors; serves as a safety net even when the Deploy button is not disabled in the UI.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([User clicks Run / Deploy]) --> B{Button disabled?}
    B -- "Run: checks hasValidationErrors\n(via useMemo in panel.tsx)" --> C{Has errors?}
    B -- "Deploy: only checks isEmpty / canAdmin\n(validation NOT checked)" --> D[Deploy proceeds]
    C -- Yes --> E[Button disabled — action blocked]
    C -- No --> F[Action triggers]
    F --> G[API: POST /api/workflows/id/deploy]
    G --> H[loadWorkflowFromNormalizedTables]
    H --> I[validateWorkflowBlocks\nblocks + edges]
    I -- errors found --> J[Return 400\nwith error details]
    I -- no errors --> K[validateWorkflowSchedules]
    K --> L[deployWorkflow]
    L --> M[200 Success]
    D --> G
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx, line 63 (link)

    Deploy button not gated on validation errors

    The PR description states "Deploy/Run buttons disabled when errors exist", but the Deploy component's disabled state only checks isDeploying, !canDeploy, and isEmpty. The hasValidationErrors flag computed in panel.tsx is never passed to this component, so a user can still click Deploy on a misconfigured workflow.

    The backend will return a 400 in that case, but the frontend should also disable the button to match the stated intent and to avoid a confusing error toast.

    A minimal fix is to accept and propagate the validation state:

    // In DeployProps
    interface DeployProps {
      activeWorkflowId: string | null
      userPermissions: WorkspaceUserPermissions
      hasValidationErrors?: boolean
      className?: string
    }
    
    // In isDisabled computation
    const isDisabled = isDeploying || !canDeploy || isEmpty || hasValidationErrors
    

    And in panel.tsx, thread the prop through:

    <Deploy activeWorkflowId={activeWorkflowId} userPermissions={userPermissions} hasValidationErrors={hasValidationErrors} />
    

Last reviewed commit: 9f05a1f

Comment on lines +19 to +21
const req = subBlockConfig.required
if (req === undefined || req === false) return false
if (req === true) return true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function variant of required silently mis-handled

The SubBlockConfig.required type includes a third variant — a callable: ((values?: Record<string, unknown>) => { field; value; not?; and? }). The current isSubBlockRequired only handles boolean and the object variant. When required is a function:

  1. It is not undefined, false, or true, so it falls through to the conditional-check branch.
  2. req.field on a function is undefined, so blockState.subBlocks[undefined as any] resolves to undefined.
  3. req.value is also undefined, so fieldValue === req.valuetrue.
  4. The function returns true, treating the field as always required.

This produces false-positive validation errors for any sub-block that conditionally declares required via a function — blocking deployment of valid workflows once such blocks are introduced.

The function should be called with the current block's sub-block values:

if (typeof req === 'function') {
  const values = Object.fromEntries(
    Object.entries(blockState.subBlocks).map(([k, v]) => [k, v?.value])
  )
  const resolved = req(values)
  // treat resolved like the object variant
  ...
}

Comment on lines +67 to +86
// Check required sub-block fields
for (const subBlockConfig of blockConfig.subBlocks) {
if (!isSubBlockRequired(subBlockConfig, blockState)) continue

const subBlockState = blockState.subBlocks[subBlockConfig.id]
const value = subBlockState?.value
const isEmpty =
value === null ||
value === undefined ||
value === '' ||
(Array.isArray(value) && value.length === 0)

if (isEmpty) {
errors.push({
blockId,
blockName: blockState.name,
message: `Missing required field: ${subBlockConfig.title || subBlockConfig.id}`,
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple errors per block for the same missing field

The loop over blockConfig.subBlocks pushes one error per required-but-empty field. For a block missing three fields this generates three separate error objects. That is fine for the backend response message, but on the frontend validationErrors.length > 0 only tells the user something is wrong — there is no UI showing which blocks or fields need attention.

Consider deduplicating to at most one error per block (the first failing field), or grouping errors by blockId, so future UI work has a clean structure to display targeted feedback.

This is a style/UX suggestion; the current behavior is not broken.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

import { getWorkflowWithValues } from '@/stores/workflows'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { validateWorkflowBlocks } from '@/lib/workflows/validation'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import ordering violates project alias conventions

The import of validateWorkflowBlocks on line 58 appears after the store imports (@/stores/…), breaking the established grouping order where @/lib/… utilities come before @/stores/…. Per the project's import-path rules, library utilities should be in a distinct group above store imports.

Suggested change
import { validateWorkflowBlocks } from '@/lib/workflows/validation'
import { validateWorkflowBlocks } from '@/lib/workflows/validation'
import { useChatStore } from '@/stores/chat/store'

(Move the @/lib/workflows/validation import up into the @/lib/… import group, before the @/stores/… group.)

Rule Used: Import patterns for the Sim application (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incomplete/broken workflows can be deployed due to hardcoded validation bypass

1 participant