How interceptors classify errors
Output’s workflow and activity interceptors decide whether a failure ends the execution, retries, or is rethrown as Temporal already classified it.Workflow
Activity (steps and evaluators)
ValidationError and TransparentFatalError both extend FatalError, so they share the fatal rows above. TransparentFatalError is only a transport: interceptors use its .cause for logs, traces, hooks, and failure details. See FatalError, ValidationError, and TransparentFatalError for when to throw each.
Steps and Evaluators
When a step or evaluator throws, we retry it automatically with exponential backoff. After all retries are exhausted, the error propagates to the workflow (typically as anActivityFailure cause chain).
Default retry behavior:
This means a failing step waits 10s, then 20s, then fails for good. You can override these per step via the
options.activityOptions property — see Step Options. Custom nonRetryableErrorTypes are added alongside FatalError; Output always keeps FatalError non-retryable.
Workflows
Errors in the workflowfn itself are classified as in the table above. Throw FatalError or ValidationError when the run must fail immediately. Ordinary bugs (for example a bad JSON.parse) retry the Workflow Task instead of failing the execution right away.
You can catch step errors with try/catch and handle them however you want:
workflow.ts
Checking error types in workflows
Errors thrown by steps and evaluators cross Temporal’s Activity boundary before they reach workflow code. Temporal serializes them into a failure cause chain, so a directerror instanceof CustomError check is not reliable inside a workflow. The same applies when Output wraps a failure in ApplicationFailure or TransparentFatalError — the error you care about is often on .cause, not the outer object.
Use hasErrorType(error, CustomError) from @outputai/core to walk the complete cause chain. It matches native instances as well as Temporal’s serialized type and name fields.
Define the custom error in a shared module imported by both the step and workflow:
types.ts
steps.ts
workflow.ts
Error metadata
Trace destinations are stored in Temporal memo and exposed as the workflow result’strace field. They are available independently of whether the workflow succeeds or fails.
Current API responses expose failures as a serialized error object with name, message, and any additional diagnostic properties captured from the original error. Activity failures also include activityType when available.
If you inspect Temporal errors directly, workflow failures remain native Temporal errors such as ApplicationFailure or ChildWorkflowFailure. Output may store the serialized original error under details[0].error on a nested failure in the .cause chain.
FatalError, ValidationError, and TransparentFatalError
Sometimes retrying won’t help — the API key is invalid, the resource doesn’t exist, or the data is fundamentally wrong. For these cases, throwFatalError or ValidationError to fail immediately without retries.
ValidationError and TransparentFatalError extend FatalError. The entire FatalError family is always non-retryable and cannot be made retryable through activity options. Throw a regular or custom Error when retry configuration should control the behavior.
All three are exported from
@outputai/core:
steps.ts
TransparentFatalError accepts an existing Error as its cause. It has the same non-retryable behavior as FatalError, but Output removes the wrapper when reporting or propagating the failure. Logs, traces, hooks, and workflow results therefore show the original error rather than a generic fatal-error message.
Schema Validation
The framework validates inputs and outputs automatically using your Zod schemas. When validation fails, it throws aValidationError with a message that tells you exactly what went wrong.
Steps and evaluators: Input is validated against inputSchema before fn runs. The return value is validated against outputSchema after fn returns. If either fails, the step fails immediately (no retries).
Workflows: Input is validated against inputSchema before the workflow starts. Output is validated against outputSchema after the workflow returns. If either fails, the execution fails.
The error message includes context like "Step lookupContact input validation failed: ..." so you can trace exactly where and why validation failed.