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
| Action | Description | Use Case |
|---|---|---|
| stop | Stop workflow immediately | Critical failures |
| continue | Continue with fallback | Non-critical operations |
| retry | Retry the node | Transient failures |
| branch | Go to error handler | Custom 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
| State | Behavior |
|---|---|
| Closed | Normal operation, tracking failures |
| Open | All requests fail immediately |
| Half-Open | Testing 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:
| Category | Retry? | Alert? | Example |
|---|---|---|---|
| Transient | Yes | After N failures | Network timeout |
| Client | No | No | Invalid input |
| Server | Yes | Yes | 500 error |
| Business | No | Depends | Insufficient funds |
| Critical | No | Immediately | Payment system down |
Defensive Design
- Validate early: Check input before processing
- Fail fast: Don't continue with bad data
- Idempotency: Safe to retry operations
- 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."