---
title: run-expired
description: A run's data passed its retention boundary, so its result can no longer be read.
type: troubleshooting
summary: Read a run's result before it expires, or return it through a channel you control.
prerequisites:
  - /docs/foundations/workflows-and-steps
related:
  - /docs/observability/retention
  - /docs/api-reference/workflow-api/start
  - /docs/foundations/hooks
---

# run-expired



## Error

```text
Run "wrun_..." completed, but its data expired at 2026-08-28T05:02:35.009Z
and is no longer readable.
```

Thrown as a `RunExpiredError` from `await run.returnValue`.

## Why this happens

A run's payloads — its input, output and error, and those of its steps — are
kept only for as long as the World's retention policy says. Its *metadata* —
id, status, timestamps — usually outlives them. So a run can be readable as a
record while its result is already gone.

Rather than hand back a placeholder that is indistinguishable from a value the
workflow genuinely returned, `returnValue` throws.

Two ways to reach it:

* **The run was started with `experimental_retention: 0`.** Its data is
  deleted the moment it reaches a terminal state, and that deletion races your
  own read of the result — and generally wins. On these runs, expect this
  error rather than treating it as an edge case. See
  [Data retention](/docs/observability/retention).
* **The run simply aged out.** It finished long enough ago that the World's
  default retention window has passed.

## How to respond

`RunExpiredError` is terminal. Retrying will not bring the data back. Catch it
and use the run's metadata to decide what you want to do.

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

export async function readResult(runId: string) {
  try {
    return await getRun(runId).returnValue
  } catch (error) {
    if (RunExpiredError.is(error)) { // [!code highlight]
      // `runStatus` is the run's terminal status when the World still has
      // it, so you can tell a successful run whose result is gone from a
      // failed one whose error is gone.
      if (error.runStatus === "completed") {
        // The run succeeded; its result is simply no longer stored.
      }
      return null
    }
    throw error
  }
}
```

The error carries `runId`, `runStatus` and `expiredAt` when the World reports
them.

### If you need the result of a zero-retention run

Do not read it back off the run. Send it somewhere you control while the run
is still executing — a step that writes it to your own store. That is the
intended pattern for `experimental_retention: 0`: the point of the option is
that the platform does not keep your data, so the platform cannot also be where
you fetch it from afterwards.

## Related

If the run is gone entirely — metadata included — the World reports it as
missing and you get a `WorkflowRunNotFoundError` instead. That means the
record itself has been cleaned up, not just its payloads.


---

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)