# Failure handlers

> Alert or clean up when a function exhausts its retries.

Send an alert or clean up after a function exhausts its retries. Attach `onFailure` to one function, or listen for `inngest/function.failed` to handle failures across an environment.

## Handle one function's final failure

TypeScript v4 accepts `onFailure` in the function configuration:
These examples use the `inngest` client from the [Quick start](/docs-markdown/durable-execution/quick-start).

```typescript {{ title: "TypeScript" }}
export const syncCatalog = inngest.createFunction(
  {
    id: "sync-catalog",
    triggers: { event: "catalog/sync.requested" },
    onFailure: async ({ error, event, step }) => {
      await step.run("record-sync-failure", () =>
        recordSyncFailure({
          failedRunId: event.data.run_id,
          message: error.message,
        })
      );
    },
  },
  async ({ event, step }) => {
    await step.run("sync-catalog", () => syncCatalogFromSource(event.data));
  }
);
```

```python {{ title: "Python" }}
import inngest

async def handle_sync_failure(ctx: inngest.Context) -> None:
    error = ctx.event.data.get("error")
    message = error.get("message") if isinstance(error, dict) else None

    async def record() -> None:
        await record_sync_failure(
            failed_run_id=ctx.event.data["run_id"],
            message=message,
        )

    await ctx.step.run("record-sync-failure", record)

@inngest_client.create_function(
    fn_id="sync-catalog",
    trigger=inngest.TriggerEvent(event="catalog/sync.requested"),
    on_failure=handle_sync_failure,
)
async def sync_catalog(ctx: inngest.Context) -> None:
    async def sync() -> None:
        await sync_catalog_from_source(ctx.event.data)

    await ctx.step.run("sync-catalog", sync)
```

The Go SDK has no per-function `onFailure` handler. Create a function triggered by `inngest/function.failed`, as shown in [Handle failures across an environment](#handle-failures-across-an-environment), and add an expression such as `event.data.function_id == "my-app-sync-catalog"` to match the failed function.

`recordSyncFailure` and `syncCatalogFromSource` are functions in your app. The failure handler is a separate Inngest function. Its `event` is the `inngest/function.failed` system event, and `event.data.run_id` identifies the original failed run. The `runId` passed directly to the failure handler identifies the handler's own run. Use a named step for retriable alerting or cleanup.

## Handle failures across an environment

Create a function triggered by `inngest/function.failed` when one team needs a shared failure stream:

```typescript {{ title: "TypeScript" }}
export const watchFailures = inngest.createFunction(
  {
    id: "watch-function-failures",
    triggers: { event: "inngest/function.failed" },
  },
  async ({ event }) => {
    console.error("function failed", event.data.function_id, event.data.run_id);
  }
);
```

```python {{ title: "Python" }}
import inngest

@inngest_client.create_function(
    fn_id="watch-function-failures",
    trigger=inngest.TriggerEvent(event="inngest/function.failed"),
)
async def watch_failures(ctx: inngest.Context) -> None:
    ctx.logger.error(
        "function failed %s %s",
        ctx.event.data["function_id"],
        ctx.event.data["run_id"],
    )
```

```go {{ title: "Go" }}
import (
	"context"
	"log"

	"github.com/inngest/inngestgo"
)

type FunctionFailedData struct {
	FunctionID string `json:"function_id"`
	RunID      string `json:"run_id"`
}

func WatchFailures(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "watch-function-failures"},
		inngestgo.EventTrigger("inngest/function.failed", nil),
		func(ctx context.Context, input inngestgo.Input[FunctionFailedData]) (any, error) {
			log.Println("function failed", input.Event.Data.FunctionID, input.Event.Data.RunID)
			return nil, nil
		},
	)
}
```

An `onFailure` handler applies to its function. The system-event trigger applies across the environment. Both respond to final failure, not every transient attempt.

## Read the error safely

The SDK serializes and deserializes the original error for `onFailure`. Custom error subclasses become ordinary `Error` objects, so do not use `instanceof` there to decide how to recover. Store a stable error code in your own data when downstream failure handling needs one. Make alerts and compensation idempotent because their handler is a separate function.