diff --git a/apps/docs/content/docs/en/blocks/condition.mdx b/apps/docs/content/docs/en/blocks/condition.mdx index 607fd64919..50c0488ad1 100644 --- a/apps/docs/content/docs/en/blocks/condition.mdx +++ b/apps/docs/content/docs/en/blocks/condition.mdx @@ -135,7 +135,7 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or ## Best Practices - **Order conditions correctly**: Place more specific conditions before general ones to ensure specific logic takes precedence over fallbacks -- **Include a default condition**: Add a catch-all condition (`true`) as the last condition to handle unmatched cases and prevent workflow execution from getting stuck +- **Use the else branch when needed**: If no conditions match and the else branch is not connected, the workflow branch will end gracefully. Connect the else branch if you need a fallback path for unmatched cases - **Keep expressions simple**: Use clear, straightforward boolean expressions for better readability and easier debugging - **Document your conditions**: Add descriptions to explain the purpose of each condition for better team collaboration and maintenance - **Test edge cases**: Verify conditions handle boundary values correctly by testing with values at the edges of your condition ranges diff --git a/apps/sim/executor/handlers/condition/condition-handler.test.ts b/apps/sim/executor/handlers/condition/condition-handler.test.ts index e6e1a3f063..07805b7517 100644 --- a/apps/sim/executor/handlers/condition/condition-handler.test.ts +++ b/apps/sim/executor/handlers/condition/condition-handler.test.ts @@ -365,7 +365,7 @@ describe('ConditionBlockHandler', () => { ) }) - it('should throw error if no condition matches and no else exists', async () => { + it('should return no-match result if no condition matches and no else exists', async () => { const conditions = [ { id: 'cond1', title: 'if', value: 'false' }, { id: 'cond2', title: 'else if', value: 'context.value === 99' }, @@ -392,9 +392,15 @@ describe('ConditionBlockHandler', () => { .mockReturnValueOnce('false') .mockReturnValueOnce('context.value === 99') - await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow( - `No matching path found for condition block "${mockBlock.metadata?.name}", and no 'else' block exists.` - ) + const result = await handler.execute(mockContext, mockBlock, inputs) + + // Should return success with no path selected (branch ends gracefully) + expect((result as any).conditionResult).toBe(false) + expect((result as any).selectedPath).toBeNull() + expect((result as any).selectedConditionId).toBeNull() + expect((result as any).selectedOption).toBeNull() + // Decision should not be set when no condition matches + expect(mockContext.decisions.condition.has(mockBlock.id)).toBe(false) }) it('falls back to else path when loop context data is unavailable', async () => { diff --git a/apps/sim/executor/handlers/condition/condition-handler.ts b/apps/sim/executor/handlers/condition/condition-handler.ts index 5551903dd9..c6cc0db62c 100644 --- a/apps/sim/executor/handlers/condition/condition-handler.ts +++ b/apps/sim/executor/handlers/condition/condition-handler.ts @@ -87,6 +87,17 @@ export class ConditionBlockHandler implements BlockHandler { block ) + // Handle case where no condition matched and no else exists - branch ends gracefully + if (!selectedConnection || !selectedCondition) { + return { + ...((sourceOutput as any) || {}), + conditionResult: false, + selectedPath: null, + selectedConditionId: null, + selectedOption: null, + } + } + const targetBlock = ctx.workflow?.blocks.find((b) => b.id === selectedConnection?.target) if (!targetBlock) { throw new Error(`Target block ${selectedConnection?.target} not found`) @@ -145,8 +156,8 @@ export class ConditionBlockHandler implements BlockHandler { ctx: ExecutionContext, block: SerializedBlock ): Promise<{ - selectedConnection: { target: string; sourceHandle?: string } - selectedCondition: { id: string; title: string; value: string } + selectedConnection: { target: string; sourceHandle?: string } | null + selectedCondition: { id: string; title: string; value: string } | null }> { for (const condition of conditions) { if (condition.title === CONDITION.ELSE_TITLE) { @@ -185,14 +196,16 @@ export class ConditionBlockHandler implements BlockHandler { if (elseConnection) { return { selectedConnection: elseConnection, selectedCondition: elseCondition } } - throw new Error( - `No path found for condition block "${block.metadata?.name}", and 'else' connection missing.` - ) + // Else exists but has no connection - treat as no match, branch ends + logger.info(`No condition matched and else has no connection - branch ending`, { + blockId: block.id, + }) + return { selectedConnection: null, selectedCondition: null } } - throw new Error( - `No matching path found for condition block "${block.metadata?.name}", and no 'else' block exists.` - ) + // No condition matched and no else exists - branch ends gracefully + logger.info(`No condition matched and no else block - branch ending`, { blockId: block.id }) + return { selectedConnection: null, selectedCondition: null } } private findConnectionForCondition(