---
title: "Error Hooks"
description: "Observe workflow, activity, and runtime errors without affecting execution"
---

Error hooks let you run code whenever a workflow, activity (step/evaluator), or runtime error occurs. Use them for logging, metrics, or alerting. When an error is emitted, all registered handlers are triggered at the same time (concurrently). Handler code is invoked asynchronously and never throws — any error in your handler is caught and logged by the framework.

## Setup

### 1. Create a hook file

Create a file that registers your handler. Import `onError` from `@outputai/core/hooks`.

```javascript
// src/error_hooks.js
import { onError } from '@outputai/core/hooks';

onError(async (payload) => {
  console.log('Error', payload);
});
```

### 2. Register the file in package.json

List your hook file(s) under `outputai.hookFiles`. Paths are relative to the package root. The worker loads these files at startup.

```json
{
  "name": "my-workflows",
  "outputai": {
    "hookFiles": ["./src/error_hooks.js"]
  }
}
```

You can register multiple files: `"hookFiles": ["./src/error_hooks.js", "./src/other_hooks.js"]`.

## The three sources

Errors are emitted from three origins. Every payload includes `eventId`, `eventDate`, `source`, and `error`. Activity- and workflow-scoped errors also include Temporal execution details.

| Source | When it fires | Fields besides `eventId`, `eventDate`, `source`, and `error` |
|--------|----------------|----------------------------------------|
| `activity` | A step or evaluator fails (after retries are exhausted). | `activityInfo`, `workflowDetails`, `outputActivityKind`, `aggregations` |
| `workflow` | The workflow function itself throws (e.g. uncaught step error or logic error). | `workflowDetails` |
| `runtime` | An error outside workflow/activity scope (e.g. in the worker/runtime). | — |

So:

- **Activity** — `eventId`, `eventDate`, `source: 'activity'`, `activityInfo`, `workflowDetails`, `outputActivityKind`, `aggregations`, `error`.
- **Workflow** — `eventId`, `eventDate`, `source: 'workflow'`, `workflowDetails`, `error` (no activity fields).
- **Runtime** — `eventId`, `eventDate`, `source: 'runtime'`, and `error` only.

`activityInfo` is Temporal's Activity execution info object; see Temporal's [`activity.Info`](https://typescript.temporal.io/api/interfaces/activity.Info) reference. `workflowDetails` is Output's serializable subset of Temporal's [`workflow.WorkflowInfo`](https://typescript.temporal.io/api/interfaces/workflow.WorkflowInfo), with fields such as `workflowId`, `runId`, `workflowType`, `parent`, and `root`.

`outputActivityKind` identifies the Output activity type. Possible values are `step`, `evaluator`, and `internal_step`.

`aggregations` contains activity-scoped attribute totals collected before the failure, such as `cost.total`, `tokens.total`, provider-specific token buckets, and `httpRequests.total`. It is `null` when no attributes were collected.

`eventId` is a UUID v4 stamped per emit — use it as a stable idempotency key when forwarding errors to external systems (Sentry, PagerDuty, your own incident pipeline). `eventDate` is the millisecond epoch timestamp for when the event was emitted. Use `source` to branch in one handler, or ignore payload fields that are undefined for that source.

## Handler safety: no throws

Your handler is wrapped in a try/catch. If the handler throws or rejects, the framework catches it, logs it (e.g. `onError hook error` with the error), and does not rethrow. Execution of the workflow and worker continues. You can focus on side effects (logging, metrics, alerts) without worrying about breaking the run.

## Example

```javascript
// src/error_hooks.js
import { onError } from '@outputai/core/hooks';

onError(async (payload) => {
  const { source, error } = payload;
  if (source === 'activity') {
    const { activityInfo, workflowDetails } = payload;
    console.error(
      `Step/evaluator failed: ${workflowDetails.workflowType} (${workflowDetails.workflowId}) / ${activityInfo.activityType} (${activityInfo.activityId})`,
      error
    );
  } else if (source === 'workflow') {
    const { workflowDetails } = payload;
    console.error(`Workflow failed: ${workflowDetails.workflowType} (${workflowDetails.workflowId})`, error);
  } else {
    console.error('Runtime error', error);
  }
});
```
