Skip to content

Commit a072602

Browse files
MaxwellCalkinclaude
andcommitted
fix: enforce workflow validation before deploy and run
The deploy endpoint only validated schedule data but never checked workflow state (block types, edges, tool references). The Run button had validation hardcoded to `false`. Redeployments (Update) skipped all pre-deploy checks entirely. Changes: - Backend: call validateWorkflowState() before deploying to reject workflows with unknown block types, dangling edges, or invalid tool references (returns 400 with details) - Frontend panel: replace hardcoded `hasValidationErrors = false` with a check that blocks are connected via edges - Frontend deploy hook: run pre-deploy checks for redeployments too, not just first deploys Fixes #3444 This PR was authored by Claude Opus 4.6 (AI), operated by @MaxwellCalkin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8c0a2e0 commit a072602

File tree

3 files changed

+28
-7
lines changed

3 files changed

+28
-7
lines changed

apps/sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
createSchedulesForDeploy,
2121
validateWorkflowSchedules,
2222
} from '@/lib/workflows/schedules'
23+
import { validateWorkflowState } from '@/lib/workflows/sanitization/validation'
2324
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
2425
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
2526
import type { WorkflowState } from '@/stores/workflows/workflow/types'
@@ -134,6 +135,21 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
134135
return createErrorResponse('Failed to load workflow state', 500)
135136
}
136137

138+
// Validate workflow state (block types, edges, tool references)
139+
const workflowValidation = validateWorkflowState({
140+
blocks: normalizedData.blocks,
141+
edges: normalizedData.edges,
142+
loops: normalizedData.loops,
143+
parallels: normalizedData.parallels,
144+
} as WorkflowState)
145+
if (!workflowValidation.valid) {
146+
const errorSummary = workflowValidation.errors.join('; ')
147+
logger.warn(
148+
`[${requestId}] Workflow validation failed for ${id}: ${errorSummary}`
149+
)
150+
return createErrorResponse(`Workflow validation failed: ${errorSummary}`, 400)
151+
}
152+
137153
const scheduleValidation = validateWorkflowSchedules(normalizedData.blocks)
138154
if (!scheduleValidation.isValid) {
139155
logger.warn(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-deployment.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,13 @@ export function useDeployment({
2828

2929
/**
3030
* Handle deploy button click
31-
* First deploy: calls API to deploy, then opens modal on success
32-
* Already deployed: opens modal directly (validation happens on Update in modal)
31+
* First deploy: runs pre-deploy checks, calls API to deploy, then opens modal on success
32+
* Already deployed: runs pre-deploy checks, then opens modal (redeployment happens in modal)
3333
*/
3434
const handleDeployClick = useCallback(async () => {
3535
if (!workflowId) return { success: false, shouldOpenModal: false }
3636

37-
if (isDeployed) {
38-
return { success: true, shouldOpenModal: true }
39-
}
40-
37+
// Always run pre-deploy checks, even for redeployments
4138
const { blocks, edges, loops, parallels } = useWorkflowStore.getState()
4239
const liveBlocks = mergeSubblockState(blocks, workflowId)
4340
const checkResult = runPreDeployChecks({
@@ -56,6 +53,10 @@ export function useDeployment({
5653
return { success: false, shouldOpenModal: false }
5754
}
5855

56+
if (isDeployed) {
57+
return { success: true, shouldOpenModal: true }
58+
}
59+
5960
setIsDeploying(true)
6061
try {
6162
const response = await fetch(`/api/workflows/${workflowId}/deploy`, {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,11 @@ export const Panel = memo(function Panel() {
356356
// Compute run button state
357357
const canRun = userPermissions.canRead // Running only requires read permissions
358358
const isLoadingPermissions = userPermissions.isLoading
359-
const hasValidationErrors = false // TODO: Add validation logic if needed
359+
360+
// Validate workflow has connected blocks (at least one edge means blocks are wired together)
361+
const hasEdges = useWorkflowStore((state) => state.edges.length > 0)
362+
const hasValidationErrors = hasBlocks && !hasEdges
363+
360364
const isWorkflowBlocked = isExecuting || hasValidationErrors
361365
const isButtonDisabled = !isExecuting && (isWorkflowBlocked || (!canRun && !isLoadingPermissions))
362366

0 commit comments

Comments
 (0)