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);
}| Parameter | Type | Required | Description |
|---|---|---|---|
code | ConnectorErrorCode | Yes | One of the seven error codes (see below). |
message | string | Yes | Human-readable error message. Shown in execution logs. |
cause | unknown | No | The 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";| Code | Value | Retryable | When to Use |
|---|---|---|---|
RATE_LIMITED | "RATE_LIMITED" | Yes | The upstream API returned HTTP 429 or equivalent rate limit response. |
AUTH_EXPIRED | "AUTH_EXPIRED" | No | Authentication credentials are invalid or expired. Triggers re-auth flow. |
NOT_FOUND | "NOT_FOUND" | No | The requested resource does not exist (HTTP 404). |
VALIDATION_ERROR | "VALIDATION_ERROR" | No | Input data failed validation. User needs to fix their configuration. |
TIMEOUT | "TIMEOUT" | Yes | The upstream API did not respond within the timeout window. |
UPSTREAM_ERROR | "UPSTREAM_ERROR" | Yes | The upstream service returned a server error (HTTP 5xx) or is unavailable. |
UNKNOWN | "UNKNOWN" | No | An 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) {
// ...
},
});| Field | Type | Description |
|---|---|---|
retryOnFailure.maxRetries | number | Maximum number of retry attempts for retryable errors. |
retryOnFailure.baseIntervalMs | number | Base delay in ms. Actual delay uses exponential backoff. |
continueOnFailure | boolean | If 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
ConnectorErrorinstead of plainError-- the execution engine treats unrecognized errors asUNKNOWN - Choose the most specific error code --
AUTH_EXPIREDtriggers re-auth,RATE_LIMITEDrespects retry-after headers - Include useful context in the message -- status codes, resource IDs, and endpoint names help with debugging
- Pass the original error as
causefor stack trace preservation - Set
continueOnFailure: trueonly for non-critical actions (logging, analytics, optional notifications) - Configure
retryOnFailurefor actions calling unreliable external services
Context and Store
Reference for ActionContext and TriggerContext — runtime context objects providing auth, property values, persistent storage, and webhook data.
Full Example
End-to-end walkthrough building a complete connector from scratch — a weather service with SecretText auth, one action, and one webhook trigger.