Error Handling
Executors have two error paths: returning a NodeExecutionResult with IsSuccess = false (soft error — routes to the error port if connected), or throwing an exception (hard error — caught by the base class error handler). Choose based on whether the error is recoverable or fatal.
Soft Error — Return a Failure Result
Return a failure result when the error is an expected operational condition (e.g. 404 from an external API, validation failure) and you want to route to the node's error output port:
// Soft error — routes to error output port
if (response.StatusCode == 404)
{
return new NodeExecutionResult
{
IsSuccess = false,
OutputPortKey = ExecutionConstants.OutputPorts.Error, // "error"
ErrorMessage = $"Resource not found: {url}",
OutputData = new Dictionary<string, object>
{
{ "status", "error" },
{ "errorCode", "NOT_FOUND" },
{ "url", url }
}
};
}
Hard Error — Throw an Exception
Throw (or let propagate) an exception when the error is unexpected or unrecoverable. BaseNodeExecutor.ErrorHandler catches it, marks the node as failed, and emits a SignalR failure event. If the node has an error output port, the engine will still attempt to route there:
// Hard error — unexpected condition, let it propagate
var config = mySettings ?? throw new InvalidOperationException(
"Settings were not initialized before execution.");
// Or throw directly:
throw new ExternalServiceException(
$"Slack API returned unexpected status {statusCode}: {body}");
Error Handling Flow
- Your code throws or returns
IsSuccess = false BaseNodeExecutor.ErrorHandlercatches the exception and setsNodeExecutionResult.IsSuccess = false- Engine checks if the node has an error output port (
isErrorPort: truein the port definition) - If error port is connected — engine routes to the next node on the error path; execution continues
- If error port is not connected — engine marks the execution as Failed and stops
Error Output Port Contract
For a node to support error routing, its NodeType must declare an error output port:
// In NodeType DB record — ports array
{
"portKey": "error",
"portType": "source",
"isErrorPort": true,
"isMainPort": false,
"label": "On Error"
}
// In the frontend renderer
<CustomHandle
type="source"
position={Position.Right}
id="error"
isErrorPort={true}
label="On Error"
style={{ top: '75%' }}
/>
// In the executor — return this portKey on failure
OutputPortKey = "error" // must match the portKey above
Retry Policy
BaseNodeExecutor reads a retry configuration from the node's settings. If present, it retries ExecuteInternalAsync the specified number of times with exponential backoff before marking the node as failed. The retry count and delay are configured per-node in the Atlas Form using the capability configuration pattern.
| Config Field | Default | Behaviour |
|---|---|---|
maxRetries | 0 | Number of retry attempts after first failure |
retryDelayMs | 1000 | Base delay between retries in ms |
retryBackoffMultiplier | 2.0 | Exponential backoff multiplier |
ErrorMessage and OutputData so the error path in the workflow can inspect the details. Reserve exceptions for programming errors and unexpected states.