Error Handling

Handle errors gracefully in your workflows

Handle errors gracefully to build robust, reliable workflows.

Error Handling Overview

Every node can fail. Good error handling ensures your workflows:

  • Recover from transient failures
  • Notify on critical errors
  • Provide meaningful error messages
  • Maintain data integrity

Node-Level Error Handling

Error Configuration

Each node has error handling options:

json
{
  "type": "http_request",
  "url": "https://api.example.com/data",
  "on_error": {
    "action": "retry",
    "max_retries": 3,
    "retry_delay_ms": 1000,
    "fallback": {
      "status": "failed",
      "message": "API unavailable"
    }
  }
}

Error Actions

ActionDescriptionUse Case
stopStop workflow immediatelyCritical failures
continueContinue with fallbackNon-critical operations
retryRetry the nodeTransient failures
branchGo to error handlerCustom error logic

Stop Action

Immediately stop workflow execution:

json
{
  "on_error": {
    "action": "stop",
    "error_message": "Critical operation failed: {{ error.message }}"
  }
}

Continue Action

Continue with default/fallback output:

json
{
  "on_error": {
    "action": "continue",
    "fallback": {
      "data": [],
      "status": "unavailable"
    }
  }
}

Retry Action

Automatically retry failed operations:

json
{
  "on_error": {
    "action": "retry",
    "max_retries": 3,
    "retry_delay_ms": 1000,
    "backoff": "exponential"
  }
}

Branch Action

Route to a dedicated error handler:

json
{
  "on_error": {
    "action": "branch",
    "target": "error_handler_node"
  }
}

Retry Configuration

Basic Retry

json
{
  "on_error": {
    "action": "retry",
    "max_retries": 3,
    "retry_delay_ms": 1000
  }
}

Exponential Backoff

Increasing delay between retries:

json
{
  "on_error": {
    "action": "retry",
    "max_retries": 5,
    "retry_delay_ms": 1000,
    "backoff": "exponential",
    "max_delay_ms": 30000
  }
}

Delay pattern: 1s → 2s → 4s → 8s → 16s

Retry Conditions

Only retry on specific errors:

json
{
  "on_error": {
    "action": "retry",
    "max_retries": 3,
    "retry_on": ["timeout", "5xx"],
    "no_retry_on": ["4xx", "validation"]
  }
}

Retry with Jitter

Add randomness to prevent thundering herd:

json
{
  "on_error": {
    "action": "retry",
    "max_retries": 3,
    "retry_delay_ms": 1000,
    "backoff": "exponential",
    "jitter": true
  }
}

Error Handler Nodes

Try-Catch Pattern

┌─────────────┐      ┌─────────────┐
│   Action    │─────▶│   Success   │
│   (try)     │      │   Path      │
└──────┬──────┘      └─────────────┘
       │ error
       ▼
┌─────────────┐      ┌─────────────┐
│   Error     │─────▶│   Handle    │
│   Handler   │      │   Error     │
└─────────────┘      └─────────────┘

Error Handler Configuration

json
{
  "id": "error_handler",
  "type": "error_handler",
  "catch_errors": ["api_error", "timeout"],
  "actions": [
    {
      "type": "send_notification",
      "channel": "slack-alerts",
      "message": "Workflow error: {{ error.message }}"
    },
    {
      "type": "set_variables",
      "variables": {
        "error_logged": true,
        "fallback_used": true
      }
    }
  ]
}

Error Context

Inside error handlers, access error details:

javascript
{{ error.type }}        // Error type (timeout, http_error, etc.)
{{ error.message }}     // Error message
{{ error.code }}        // Error code if available
{{ error.node }}        // Node that failed
{{ error.timestamp }}   // When error occurred
{{ error.attempt }}     // Retry attempt number
{{ error.stack }}       // Stack trace (debug mode)

Global Error Handling

Workflow-Level Handler

Catch any unhandled errors:

json
{
  "workflow": {
    "on_error": {
      "handler": "global_error_handler",
      "notify": ["admin@company.com"],
      "log_level": "error"
    }
  }
}

Global Error Handler Node

json
{
  "id": "global_error_handler",
  "type": "error_handler",
  "is_global": true,
  "actions": [
    {
      "type": "send_email",
      "to": "{{ workflow.error_recipients }}",
      "subject": "Workflow Failed: {{ workflow.name }}",
      "body": "Error: {{ error.message }}\n\nExecution ID: {{ execution.id }}"
    },
    {
      "type": "database",
      "operation": "insert",
      "table": "error_logs",
      "data": {
        "workflow_id": "{{ workflow.id }}",
        "error": "{{ error.message }}",
        "context": "{{ stringify(error) }}"
      }
    }
  ]
}

Error Types

HTTP Errors

json
{
  "on_error": {
    "action": "branch",
    "conditions": [
      {
        "error_type": "http_4xx",
        "target": "handle_client_error"
      },
      {
        "error_type": "http_5xx",
        "target": "handle_server_error"
      },
      {
        "error_type": "timeout",
        "target": "handle_timeout"
      }
    ]
  }
}

Validation Errors

json
{
  "type": "validate",
  "schema": {...},
  "on_error": {
    "action": "branch",
    "target": "validation_failed",
    "pass_validation_errors": true
  }
}

Access validation details:

javascript
{{ error.validation_errors }}  // Array of validation issues
{{ error.validation_errors[0].field }}
{{ error.validation_errors[0].message }}

Custom Errors

Throw custom errors from code:

json
{
  "type": "execute_code",
  "code": "if (input.amount < 0) { throw new Error('Amount must be positive'); } return input;"
}

Timeout Handling

Node Timeouts

json
{
  "type": "http_request",
  "url": "...",
  "timeout_ms": 30000,
  "on_timeout": {
    "action": "retry",
    "max_retries": 2
  }
}

Workflow Timeouts

json
{
  "workflow": {
    "timeout_ms": 300000,
    "on_timeout": {
      "action": "stop",
      "notify": true,
      "cleanup": "cleanup_handler"
    }
  }
}

Circuit Breaker

Prevent cascading failures:

json
{
  "type": "http_request",
  "url": "...",
  "circuit_breaker": {
    "enabled": true,
    "failure_threshold": 5,
    "timeout_ms": 60000,
    "half_open_requests": 3
  }
}

Circuit States

StateBehavior
ClosedNormal operation, tracking failures
OpenAll requests fail immediately
Half-OpenTesting if service recovered

Compensation/Rollback

Saga Pattern

For distributed transactions:

[Create Order] → [Reserve Inventory] → [Process Payment]
       ↓                 ↓                    ↓
  [Cancel Order]   [Release Inventory]  [Refund Payment]
       ↑                 ↑                    ↑
       └─────────────────┴─── on failure ────┘

Compensation Configuration

json
{
  "id": "create_order",
  "type": "http_request",
  "compensation": {
    "node": "cancel_order",
    "trigger_on": ["downstream_failure"]
  }
}

Rollback Handler

json
{
  "id": "rollback_handler",
  "type": "rollback",
  "steps": [
    {
      "node": "cancel_order",
      "condition": "{{ nodes.create_order.success }}"
    },
    {
      "node": "release_inventory",
      "condition": "{{ nodes.reserve_inventory.success }}"
    }
  ]
}

Dead Letter Queue

Handle persistently failing items:

json
{
  "type": "loop",
  "array": "{{ input.items }}",
  "on_item_error": {
    "action": "continue",
    "dead_letter": {
      "enabled": true,
      "queue": "failed_items",
      "max_retries": 3
    }
  }
}

Processing Dead Letters

json
{
  "type": "schedule",
  "cron": "0 * * * *",
  "workflow": "process_dead_letters",
  "input": {
    "queue": "failed_items",
    "batch_size": 100
  }
}

Alerting

Error Notifications

json
{
  "on_error": {
    "action": "stop",
    "alerts": [
      {
        "channel": "slack",
        "webhook": "{{ env.SLACK_WEBHOOK }}",
        "message": ":x: Workflow failed: {{ workflow.name }}"
      },
      {
        "channel": "email",
        "to": ["ops@company.com"],
        "subject": "Workflow Error Alert"
      },
      {
        "channel": "pagerduty",
        "severity": "critical",
        "condition": "{{ error.type == 'payment_failed' }}"
      }
    ]
  }
}

Alert Throttling

Prevent alert storms:

json
{
  "alerts": {
    "throttle": {
      "window_ms": 300000,
      "max_alerts": 5
    }
  }
}

Logging

Error Logging

json
{
  "on_error": {
    "log": {
      "level": "error",
      "include": ["error", "input", "context"],
      "exclude": ["sensitive_field"]
    }
  }
}

Structured Logs

json
{
  "type": "execute_code",
  "code": "console.error({ event: 'payment_failed', customer_id: input.customer_id, amount: input.amount, error: error.message }); throw error;"
}

Best Practices

Error Categories

Categorize errors for appropriate handling:

CategoryRetry?Alert?Example
TransientYesAfter N failuresNetwork timeout
ClientNoNoInvalid input
ServerYesYes500 error
BusinessNoDependsInsufficient funds
CriticalNoImmediatelyPayment system down

Defensive Design

  1. Validate early: Check input before processing
  2. Fail fast: Don't continue with bad data
  3. Idempotency: Safe to retry operations
  4. Graceful degradation: Fallback to limited functionality

Error Messages

javascript
// Good - actionable error message
"Payment failed for customer {{ input.customer_id }}: Card declined (code: {{ error.code }}). Please update payment method."

// Bad - vague error message
"An error occurred."

Next Steps