Skip to main content
Arcanflows

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": ["[email protected]"],
      "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": ["[email protected]"],
        "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