---
title: max-events-exceeded
description: "A workflow run reached the World's per-run event limit and was failed."
docs_index: /llms.txt
lastUpdated: 2026-10-13
type: troubleshooting
summary: Run long-running workflows on the QuickJS VM with snapshotting, which is exempt from the default event limit, or split the work into child workflows.
prerequisites:
  - /docs/how-it-works/event-sourcing
related:
  - "/docs/configuration/runtime-tuning#workflow_vm"
  - /docs/errors/replay-timeout
  - /cookbook/advanced/child-workflows
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Every step, hook, sleep, and lifecycle transition adds events to a run's [event log](/docs/how-it-works/event-sourcing). The World supplies a per-run ceiling on that log, and the runtime fails a run whose log reaches it with the `MAX_EVENTS_EXCEEDED` error code. The ceiling is a backstop against runaway loops that would otherwise grow a log without bound.

For the Vercel World's ceiling, see [Workflow run limits](https://vercel.com/docs/workflows/pricing#workflow-run-limits). The Local World's ceiling is set by [`WORKFLOW_MAX_EVENTS`](/docs/configuration/runtime-tuning#workflow_max_events).

## Error message

```text
Workflow exceeded the maximum of <limit> events per run
├▶ hint: Run long-running workflows on the QuickJS VM with snapshotting by setting WORKFLOW_VM=quickjs ...
╰▶ docs: https://workflow-sdk.dev/err/max-events-exceeded
```

Before a run reaches the limit, the runtime logs a warning in your function logs at 50%, 80%, and 95% of the ceiling:

```text
Workflow run is approaching the per-run event limit (<count> of <limit> events, 50%). ...
```

## Why it happens

- **The workflow is long-running by design.** Agents, chat sessions, and loops that wait on hooks or sleeps for days accumulate events with every iteration.
- **The workflow has unbounded work.** For example, a loop that keeps creating steps without a termination condition.

## How to fix it

### Run on the QuickJS VM with snapshotting

> The QuickJS engine is experimental, and its global surface differs from the default engine. Review the [differences](/docs/configuration/runtime-tuning#workflow_vm) before switching an existing deployment.

Runs that execute on the QuickJS VM with snapshotting enabled are exempt from the default per-run event limit. A snapshotted run restores its VM memory and replays only the events recorded since the last snapshot, so its resume cost doesn't grow with the length of the log.

Set `WORKFLOW_VM=quickjs` in the environment that starts runs (the process that calls [`start()`](/docs/api-reference/workflow-api/start)). On Vercel that's your project's environment variables:

```bash
WORKFLOW_VM=quickjs
```

Snapshotting is on by default under QuickJS. Leave [`WORKFLOW_SNAPSHOT_THRESHOLD`](/docs/configuration/runtime-tuning#workflow_snapshot_threshold) unset, or set it to a positive integer. `WORKFLOW_SNAPSHOT_THRESHOLD=0` disables snapshotting, and those runs keep the limit.

A run qualifies when its `executionContext` records `workflowVm: "quickjs"` and a positive `snapshotThreshold`. `start()` records both when the run is created, so:

- Only runs started after the change are exempt. Runs keep the VM and snapshot policy they started with.
- Runs that have already failed with `MAX_EVENTS_EXCEEDED` are not revived. Start them again.
- On the Local World and Postgres World, snapshots are only stored when [`WORKFLOW_SNAPSHOT_ALLOW_UNENCRYPTED=1`](/docs/configuration/runtime-tuning#workflow_snapshot_allow_unencrypted) is set. Without it, exempt runs still replay the full log on every invocation.

### Split the work into child workflows

If a run doesn't need to be a single long-lived run, start [child workflows](/cookbook/advanced/child-workflows) for each batch or session and keep the parent's log small. This also works on the default engine.

### Fix unbounded loops

If the run wasn't expected to grow this large, check the workflow function for loops that create steps, hooks, or sleeps without terminating.

## This error cannot be caught

`MAX_EVENTS_EXCEEDED` is recorded by the runtime, not thrown into your workflow function. Check the run status from outside instead:

```typescript lineNumbers
import { getRun } from "workflow/api";

const run = getRun("wrun_abc123");
if ((await run.status) === "failed") {
  console.error("Run failed");
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)