Skip to main content
When something throws in your workflow, Output handles it differently depending on where it happens. Steps and evaluators are retried automatically. In workflow code, only explicit fatal failures end the run immediately — other throws retry the Workflow Task. You can control this with retry options, non-retryable errors, and try/catch.

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 an ActivityFailure 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 workflow fn 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
If you don’t catch a step failure, it surfaces to the workflow as a Temporal failure and the workflow fails (unless you handle it).

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 direct error 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
Always rethrow errors that do not match the type the workflow explicitly handles.

Error metadata

Trace destinations are stored in Temporal memo and exposed as the workflow result’s trace 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, throw FatalError 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 a ValidationError 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.