workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
86 lines (68 loc) • 3.01 kB
text/mdx
---
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
---
## 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.