Triggo Documentation
SDK

Error Handling

Reference for ConnectorError — structured errors with error codes, automatic retryability detection, and best practices for robust connectors.

Error Handling

The ConnectorError class provides structured error reporting with error codes that the execution engine uses to determine retry behavior, user notifications, and pipeline state transitions.

ConnectorError

import { ConnectorError } from "@triggo/connector-sdk";
import { CONNECTOR_ERROR_CODES } from "@triggo/shared";

throw new ConnectorError(
  CONNECTOR_ERROR_CODES.UPSTREAM_ERROR,
  "Service returned 503",
  originalError, // optional cause
);

Constructor

class ConnectorError extends Error {
  readonly code: ConnectorErrorCode;
  readonly retryable: boolean;

  constructor(code: ConnectorErrorCode, message: string, cause?: unknown);
}
ParameterTypeRequiredDescription
codeConnectorErrorCodeYesOne of the seven error codes (see below).
messagestringYesHuman-readable error message. Shown in execution logs.
causeunknownNoThe original error for debugging. Passed to Error.cause.

The retryable property is set automatically based on the error code.

Error Codes

All error codes are defined in @triggo/shared:

import { CONNECTOR_ERROR_CODES } from "@triggo/shared";
CodeValueRetryableWhen to Use
RATE_LIMITED"RATE_LIMITED"YesThe upstream API returned HTTP 429 or equivalent rate limit response.
AUTH_EXPIRED"AUTH_EXPIRED"NoAuthentication credentials are invalid or expired. Triggers re-auth flow.
NOT_FOUND"NOT_FOUND"NoThe requested resource does not exist (HTTP 404).
VALIDATION_ERROR"VALIDATION_ERROR"NoInput data failed validation. User needs to fix their configuration.
TIMEOUT"TIMEOUT"YesThe upstream API did not respond within the timeout window.
UPSTREAM_ERROR"UPSTREAM_ERROR"YesThe upstream service returned a server error (HTTP 5xx) or is unavailable.
UNKNOWN"UNKNOWN"NoAn unexpected error that does not fit other categories.

Retryable Errors

Three error codes are automatically marked as retryable:

const RETRYABLE_ERROR_CODES: ReadonlySet<ConnectorErrorCode> = new Set([
  "RATE_LIMITED",
  "TIMEOUT",
  "UPSTREAM_ERROR",
]);

When a retryable error is thrown and the action has errorHandlingOptions.retryOnFailure configured, the execution engine automatically retries with exponential backoff.

ErrorHandlingOptions

Configure retry and failure behavior per action:

import { createAction } from "@triggo/connector-sdk";

const myAction = createAction({
  name: "send_notification",
  displayName: "Send Notification",
  description: "Sends a push notification.",
  props: {},
  errorHandlingOptions: {
    retryOnFailure: {
      maxRetries: 3,
      baseIntervalMs: 1000,
    },
    continueOnFailure: false,
  },
  async run(context) {
    // ...
  },
});
FieldTypeDescription
retryOnFailure.maxRetriesnumberMaximum number of retry attempts for retryable errors.
retryOnFailure.baseIntervalMsnumberBase delay in ms. Actual delay uses exponential backoff.
continueOnFailurebooleanIf true, downstream pipeline nodes execute even when this action fails.

Usage Patterns

Wrapping HTTP Responses

import { ConnectorError } from "@triggo/connector-sdk";
import { CONNECTOR_ERROR_CODES } from "@triggo/shared";

async function handleResponse(response: Response): Promise<unknown> {
  if (response.ok) {
    return response.json();
  }

  if (response.status === 401 || response.status === 403) {
    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.AUTH_EXPIRED,
      `Authentication failed: ${response.status}`,
    );
  }

  if (response.status === 404) {
    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.NOT_FOUND,
      "Resource not found",
    );
  }

  if (response.status === 429) {
    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.RATE_LIMITED,
      "Rate limit exceeded",
    );
  }

  if (response.status >= 500) {
    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.UPSTREAM_ERROR,
      `Server error: ${response.status} ${response.statusText}`,
    );
  }

  throw new ConnectorError(
    CONNECTOR_ERROR_CODES.UNKNOWN,
    `Unexpected response: ${response.status}`,
  );
}

Wrapping Network Errors

async function safeFetch(url: string, options: RequestInit): Promise<Response> {
  try {
    return await fetch(url, options);
  } catch (error) {
    if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("network"))) {
      throw new ConnectorError(
        CONNECTOR_ERROR_CODES.UPSTREAM_ERROR,
        "Network error: unable to reach the service",
        error,
      );
    }

    if (error instanceof DOMException && error.name === "AbortError") {
      throw new ConnectorError(
        CONNECTOR_ERROR_CODES.TIMEOUT,
        "Request timed out",
        error,
      );
    }

    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.UNKNOWN,
      "Unexpected error during request",
      error,
    );
  }
}

Validating Input

async run(context) {
  const email = context.propsValue.email as string;

  if (!email.includes("@")) {
    throw new ConnectorError(
      CONNECTOR_ERROR_CODES.VALIDATION_ERROR,
      `Invalid email address: "${email}"`,
    );
  }

  // proceed with valid input...
}

Best Practices

  • Always use ConnectorError instead of plain Error -- the execution engine treats unrecognized errors as UNKNOWN
  • Choose the most specific error code -- AUTH_EXPIRED triggers re-auth, RATE_LIMITED respects retry-after headers
  • Include useful context in the message -- status codes, resource IDs, and endpoint names help with debugging
  • Pass the original error as cause for stack trace preservation
  • Set continueOnFailure: true only for non-critical actions (logging, analytics, optional notifications)
  • Configure retryOnFailure for actions calling unreliable external services

On this page