# realm run: commands that act

`realm run` has seventeen subcommands. This page covers the eleven that change a run or the store. The six that only read are in [`realm run`: commands that read](https://realmengine.dev/docs/reference/cli/realm-run-reading.md). Every output shown came from a run of the command.

| Subcommand                | What it does                                                  | Acts when                   |
| ------------------------- | ------------------------------------------------------------- | --------------------------- |
| [`respond`](#respond)     | Answers a gate.                                               | Always                      |
| [`advance`](#advance)     | Runs what a run owes the engine, with no model.               | Always                      |
| [`resume`](#resume)       | Makes a failed step runnable again.                           | Always                      |
| [`abandon`](#abandon)     | Ends an open run.                                             | Always                      |
| [`cleanup`](#cleanup)     | Abandons every open run that has been idle for a given time.  | Unless `--dry-run` is given |
| [`reclaim`](#reclaim)     | Frees a step that was started and never finished.             | Only with `--force`         |
| [`drain`](#drain)         | Runs the cleanup steps a finished run still owes.             | Only with `--force`         |
| [`purge`](#purge)         | Deletes finished runs.                                        | Only with `--force`         |
| [`gc`](#gc)               | Deletes files that a crash left in the store.                 | Only with `--force`         |
| [`reconcile`](#reconcile) | Rebuilds the index of idempotency keys.                       | Unless `--dry-run` is given |
| [`migrate`](#migrate)     | Records the ending of runs that were written by old versions. | Only with `--force`         |

A duration is a whole number followed by `d`, `h` or `m`: `30d`, `6h`, `10m`. A run ID that is not in the store gets `Run not found: <id>` and exit code 1.

When the `@sensigo/realm` the workflow's code imports is not the version the command runs, the command prints [`REALM_RELEASE_LINE_MISMATCH`](https://realmengine.dev/docs/reference/workflow/loader-diagnostics.md#warning-codes) to stderr once per copy and goes on. This was added in 0.46.0.

## `respond`

```text
realm run respond <run-id> --gate <gate-id> --choice <choice> [--by <name>] [--project <dir>] [--extensions-module <path>]
```

Answers the gate a run is waiting at. `realm run inspect <run-id>` prints the gate's ID and its choices.

| Flag                         | Required | What it does                                                                                                                                                                                                                                                                                      |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gate <gate-id>`           | Yes      | The ID of the open gate.                                                                                                                                                                                                                                                                          |
| `--choice <choice>`          | Yes      | One of the gate's choices.                                                                                                                                                                                                                                                                        |
| `--by <name>`                | No       | Who made the choice. Recorded with the answer as given, and not checked. At most 200 characters, no control characters. Added in 0.46.0.                                                                                                                                                          |
| `--project <dir>`            | No       | Used only for a workflow registered without a project folder (made by an agent or from a string): the folder whose `realm.yaml` and code it loads. Default: the current folder. A workflow registered from a folder loads its code from there, and `--project` is not used (the command says so). |
| `--extensions-module <path>` | No       | Loads this code file in place of the files named by the workflow's `extensions`.                                                                                                                                                                                                                  |

```bash
realm run respond 3ebc1158-1d29-41b5-9ca5-df054a681b58 --gate 0d499c26-a6b4-406f-ae29-6d6ef5c75fcb --choice approve
```

```text
Responded: 3ebc1158-1d29-41b5-9ca5-df054a681b58 | choice 'approve' | new state 'running'
```

`new state` is the run's phase after the answer: `running` if steps remain, `completed` if the gate's step was the last.

When the answer leaves `auto` steps that only the engine can run, one more line names them and the command that runs them (`runs it` for one step, `runs them until a step opens a question, fails or ends the run` for more: see [`advance`](#advance)), where that command loads the steps' project code from (the workflow's own project folder, whatever folder the shell is in; for a workflow made without one, the folder it runs in or its `--project`), and that the environment is that shell's. Added after version 0.46.0:

```text
Responded: b178179a-998d-457e-85e6-6d38439d0585 | choice 'approve' | new state 'running'
Owed to the engine: 'process', 'notify' — realm run advance b178179a-998d-457e-85e6-6d38439d0585 runs them until a step opens a question, fails or ends the run, with the project code under /home/me/project, in the environment of the shell it runs in.
If a realm workflow run or realm agent is still waiting on this run, it goes on by itself; the line above is for when none is.
```

When the workflow's own project folder holds no project code (no `realm.yaml`, no extension module), the line says `with no project code (nothing to load under <folder>)` instead. When `--project` is given for a workflow that has its own project folder, the first line says it was not used: `--project <dir> was not used: workflow '<id>' has its own project, <folder>, and its code is loaded from there.`, or, when that folder holds no project code, `… has its own project, <folder> (no project code there).` Both added after version 0.46.0. With `--extensions-module`, that folder's extension modules are not loaded, and the line ends `…, and its realm.yaml is loaded from there.`, or `… <folder> (no realm.yaml there).` when it has none.

When the answer leaves an agent step ready, the line says so in the words `realm run advance` uses, with the command that drives it. Added after version 0.46.0:

```text
Responded: 4168cf62-7ae2-4ed9-a688-e149ccbe06a6 | choice 'approve' | new state 'running'
An agent step is ready: 'finish' — drive it with realm agent --run-id 4168cf62-7ae2-4ed9-a688-e149ccbe06a6 --provider <provider> --model <model>
If a realm workflow run or realm agent is still waiting on this run, it goes on by itself; the line above is for when none is.
```

After the lines that name a command — `Owed to the engine: …`, `An agent step is ready: …` — one more line says that a `realm workflow run` or `realm agent` still waiting on the run goes on by itself (`the lines above are` when there are two): the run's record does not show whether one is waiting, so the line holds either way. Added after version 0.46.0.

When the answer leaves nothing that can run from here — no agent step ready, no owed step that can run, only `auto` steps that cannot run — each such step is named, then the way out. Added after version 0.46.0:

```text
Responded: 77772f0c-a80d-49d3-b665-4ac186186fd1 | choice 'approve' | new state 'running'
'compute' cannot run (input_schema): Invalid input for step 'compute': the input must have required property 'n'.
Run 77772f0c-a80d-49d3-b665-4ac186186fd1 stays open (phase 'running'): for 'compute', the engine gives it no input, so correct its input_schema and register the workflow again; then, after a fix, realm run advance 77772f0c-a80d-49d3-b665-4ac186186fd1 — or end it: realm run abandon 77772f0c-a80d-49d3-b665-4ac186186fd1
```

A step that needs a handler or adapter this program lacks ends with its own way out (`— load the missing extension, or run the step on a runner that has it.`), and when no step is refused before its claim the last line is `To end the run instead: realm run abandon <id>`

When the answer leaves nothing to name but a step in flight in another program, the line says to wait for it, in the words `realm run advance` uses. Added after version 0.46.0:

```text
Responded: 5c1e8a42-3f0b-4d7e-9a61-2b8f0c4d7e13 | choice 'approve' | new state 'running'
'pack' is in flight in another program — wait for it, or see realm run inspect 5c1e8a42-3f0b-4d7e-9a61-2b8f0c4d7e13
```

`respond` records the answer. Without `--by` the answer names nobody, and `realm run inspect` shows `(not stated)`. With `--by`, the `Responded:` line names the answerer: `Responded: <id> | choice '<choice>' | answered by <name> (as stated) | new state '<phase>'`. A name is never taken from the operating system or from `REALM_OPERATOR`: those name a program, and `respond` uses `REALM_OPERATOR` only for the cleanup steps its answer runs. An empty, long or control-character name is refused before the run is read:

```text
--by: empty; nothing was recorded. Give a name of at most 200 characters with no control characters, or leave it out.
```

A `REALM_OPERATOR` that cannot be used refuses the answer too, even when the workflow has no cleanup steps, because `respond` checks it before it reads the run:

```text
REALM_OPERATOR: contains a control character; it is written as the program's name on any cleanup steps the answer lets run, and respond checks it before reading the run, so nothing was recorded. Unset it or give it a name of at most 200 characters with no control characters.
```

The middle word is `empty`, `longer than 200 characters` or `contains a control character`. `respond` never passes a `claim_token`; the answer's proof reads `no claim_token passed` on `inspect`.

A guard step that the answer makes ready is decided in the same write, and what it did is printed before the `Responded:` line. No other step runs: the steps that follow run when a driver next calls the run.

A guard that passed, with steps still to run:

```text
Guard step 'only_if_shipping' passed.
Responded: 989c0619-1bda-4b2e-b5e3-4d33d6519a67 | choice 'ship' | new state 'running'
An agent step is ready: 'ship' — drive it with realm agent --run-id 989c0619-1bda-4b2e-b5e3-4d33d6519a67 --provider <provider> --model <model>
If a realm workflow run or realm agent is still waiting on this run, it goes on by itself; the line above is for when none is.
```

A guard that ended the run. `Reason:` is the guard's `abort_message`, and is left out when the guard has none:

```text
Guard step 'only_if_shipping' aborted the run.
Reason: The order was held.
Responded: c267f37e-bddb-46aa-aea2-b14f62ba358e | choice 'hold' | new state 'aborted'
```

The first line is one of three sentences: `Guard step '<step>' aborted the run.`, `Guard step '<step>' failed with a resolution error. Run is terminated.`, or `Guard step '<step>' passed and completed the run.` A guard that failed is listed as a failed step `realm run resume` takes, so the second sentence goes on with the way back in: `To make the failed step runnable again: realm run resume <id> --from <step>` (added after version 0.46.0). When the run ended and it has cleanup steps, they run in this process, and one line follows for each cleanup step this ending ran or left pending: `finalizer '<name>': <status>`. A cleanup step that completed or failed at an earlier ending, before `realm run resume`, does not run again and gets no line. When one is left `pending` (this program has no handler for it), the next line names the command that runs it: `Cleanup step left pending: '<name>' — to run it with code that has its handler: realm run drain <id> --force` (added after version 0.46.0).

Giving the same answer again prints the `Responded:` line again, naming the answerer the record holds (`answered by <name> (as stated)`, or `answered by (not stated)` when you gave `--by` and the record holds no name); it records no new answer.

Without `--by`, a repeat of an answer that names nobody prints no `answered by` part, as the first answer did. A recorded name that cannot be printed, which only a record written outside these commands can hold, is shown as `answered by a recorded name that cannot be printed (control characters, or not a name with its source)`, with or without `--by`. A repeat of the recorded answer is printed the same way after the run has ended, with the run's phase (for example `new state 'abandoned'`), and exits 0. The refusal "The run has ended" below is for an answer to a question that was still open when the run ended, for example one whose time ran out with `on_expiry: abort`.

An answer that arrives after the gate's time is up is not recorded, and for a gate that was settled with its default choice `Not recorded:` takes the place of `Responded:`. The lines after it are the ones above: what the run owes (added after version 0.46.0). When this answer is the call that carried out the expiry, its first line says so, as `realm run advance`'s does (where the expiry's choice made a guard ready, `respond` prints the guard's line on its own, after the first line; `realm run advance` adds the guard's sentence to the end of its first line): `⚠ gate '<gate>' on '<step>' had expired <how long> before this call — this respond call first carried out its declared settle_default: the default choice '<choice>' was recorded (enacted_via: respond).`, or `… its declared abort: the run ended (enacted_via: respond).` (added after version 0.46.0). See [An answer after the time is up](https://realmengine.dev/docs/reference/workflow/gates.md#an-answer-after-the-time-is-up).

**Exit code:** 0 if the call succeeded, otherwise 1. An answer that was recorded exits 0, also when the guard it made ready aborted the run. A late answer that names the choice the gate was settled with exits 0, although it was not recorded. A refused answer exits 1. This differs from `realm workflow run`, which exits 1 for a run that ended as aborted.

The refusals:

| Case                                                           | Message                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gate` is not the open gate                                  | `Gate 'wrong' is not the open gate and matches no committed resolution.`                                                                                                                                                                                                                                                                                                                                                                           |
| `--choice` is not one of the choices                           | `Choice 'maybe' is not valid. Expected one of: approve, reject`                                                                                                                                                                                                                                                                                                                                                                                    |
| The gate was answered differently                              | `Gate '70d76b3b-…' was already resolved with choice 'approve' — your choice 'reject' was not recorded.`                                                                                                                                                                                                                                                                                                                                            |
| The gate's time was up, and it was settled with another choice | `Gate '80e024ee-…' was settled by timeout with choice 'hold' — your choice 'ship' was not recorded.`, after the `⚠` line when this answer carried out the expiry. Then what the guard did, if this answer carried out the expiry — a guard that failed goes on with the way back in, `To make the failed step runnable again: realm run resume <id> --from <step>` (added after version 0.46.0) — the `Not recorded:` line, and what the run owes. |
| The run has ended                                              | `Run '00b33778-…' is terminal (aborted); cannot submit a gate response — an aborted run is never resumed; 'realm run purge 00b33778-…' previews what it would remove.` The words after the dash name the way out that kind of ending has, as `submit_human_response` names it: see [A run that has ended](https://realmengine.dev/docs/reference/mcp/tools.md#a-run-that-has-ended).                                                                                                   |

## `advance`

```text
realm run advance <run-id> [--project <dir>] [--extensions-module <path>]
```

Added after version 0.46.0. Runs what a run owes the engine, from this shell — no model provider, no key: first, when the open question's time is up and it declares `on_expiry`, that default or abort (the preview names it as `the expired question on '<step>' (its declared <on_expiry>)`, and for a declared default goes on `; then it runs what that leaves owed until a step opens a question, fails or ends the run.`, and the command prints the line from its reply before the steps it runs: `⚠ gate '<gate>' on '<step>' had expired <how long> before this call — this advance call first carried out its declared settle_default: the default choice '<choice>' was recorded (enacted_via: advance).`, or `… its declared abort: the run ended …`; when the default's choice made a guard ready, the guard's sentence follows on the same line: `… (enacted_via: advance). Guard step '<guard>' passed.`); then the guards and `auto` steps that are ready. It runs them one at a time, and the steps the run owes after them, and stops at the first step that opens a question, fails or ends the run: a step named after that one does not run in that call. When the preview names more than one, its line says so: `Owed to the engine: 'approve', 'fetch'; it runs them until a step opens a question, fails or ends the run.` It loads the project's extensions exactly as `respond` does (`--project`, `--extensions-module`) — from the workflow's own project folder, whatever folder the shell is in, or, for a workflow made without one, from `--project` or the folder it runs in — names this program with `REALM_OPERATOR` or the OS user (a `REALM_OPERATOR` that cannot be used prints one line and exits 1 before any work), and prints what it is about to do before it runs anything:

```text
Advancing run <id> (workflow 'cli-owed-wf') with no project code (nothing to load under /home/me/project), in this shell's environment.
This program: tester (from REALM_OPERATOR) · project code: neither side records project code.
Last recorded driver: none recorded.
Owed to the engine: 'after'.
→ after
Stopped: an agent step is ready: 'finish' — drive it with realm agent --run-id <id> --provider <provider> --model <model>
If a realm workflow run or realm agent is still waiting on this run, it goes on by itself; the line above is for when none is.
Run <id>: phase 'running'
```

Each guard it decides prints `Guard step '<guard>' passed.`, also when a later step completes the run, in the order the steps ran: a guard decided before a later step is printed before that step's line. The guard that ended the run prints its sentence (`Guard step '<guard>' aborted the run.`) and `Reason:` when it has one. A guard that failed — its path found nothing — is a step that failed: a `Stopped: '<guard>' failed: <reason>` line follows, and the exit code is 1, as for an `auto` step that failed.

The first line names the folder the project code is loaded from, or says `with no project code (nothing to load under <folder>)` when that folder holds none (no `realm.yaml`, no extension module); a `--project` the workflow's own project folder overrides is said on the next line, `--project <dir> was not used: workflow '<id>' has its own project, <folder>, and its code is loaded from there.`, or `… <folder> (no project code there).` when it holds none (both added after version 0.46.0). With `--extensions-module`, the first line names that module, `with the module <path> (--extensions-module)`, followed by `and the realm.yaml of <folder>` only when that folder has a `realm.yaml`, which the command then reads; the folder's extension modules are not loaded, so the `--project` line ends `…, and its realm.yaml is loaded from there.`, or `… <folder> (no realm.yaml there).` when it has none. `--project` is used only for a workflow registered without a project folder (`realm run advance --help` says so). The expiry line is printed when the call carries the expiry out, before the steps it led to; every other line in the reply's `warnings` is printed as `⚠ <line>` after the steps that ran (added after version 0.46.0, which printed only the expiry line, on stderr, after the steps). At an open question the command runs nothing, and its `Nothing is owed to the engine: a question is open — realm run respond …` line is composed from the run's record as the command read it: when another program answers the question meanwhile, the steps that answer made ready are left for the next `realm run advance`. That command, here and in the `Stopped:` line of a question the call opened, gives the choices as `--choice <one of: approve, reject>`, a placeholder to replace, or the choice itself when the question has only one.

The preview's `project code` words compare the code this program loaded with what the run last recorded:

| Words                                                       | Means                                                                                                                                                                             |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `same as the run's last record`                             | The same files with the same hashes.                                                                                                                                              |
| `differs from the run's last record`                        | Comparable, and different: a code file, an entry module, the `realm.yaml` or the `--extensions-module` override changed.                                                          |
| `not comparable with the run's last record`                 | The run records project code and this program loaded none, the two fingerprints were taken under different rules, one was cut short at its size limit, or one side's load failed. |
| `not comparable — the run has recorded no project code yet` | This program loaded project code, and the run has recorded none: no program with project code has run a step of it yet, as on a run just created. It is not a mismatch.           |
| `neither side records project code`                         | Neither the run nor this program loaded project code.                                                                                                                             |

`Last recorded driver:` names the program on the run's newest evidence entry that names one, with that entry's step and time: `<program> at step '<step>', <time>.`, or `none recorded.` A step's own entry names the program that ran it when that program recorded its name; an answer to a question names none, and neither does a guard's decision. So the program that answered a question is not named here: after an answer whose write ran no cleanup step, the line still names the program that ran the question's step and opened the question. `realm run inspect` shows who answered, on that step's `Answer:` line (`answered by <name>`, the name `--by` gave, or `answered by (not stated)`). When newer entries name no program, the line ends `; 1 newer entry records no driver.` (or `; <n> newer entries record no driver.`). Each one counts but a question's answer, the entry its expiry writes and a guard's decision: a step run by a program that recorded no name (a library call made without `driver`, for one), and an entry realm writes without running a step, such as the one `realm run reclaim` writes for the step whose claim it frees.

When another program holds an owed step, the preview says so before anything runs, once, with the program and the time: `In flight: '<step>' is in flight, taken by <program> since <time>.`

`Stopped:` lines say why it stopped, one line for each reason that holds, in this order: a step that failed (`'<step>' failed: <error>`, only when the run's record lists the step as failed, and never for a step it started whose outcome was not recorded: when another program's run of that step failed, the step's own line says so, `…, and failed; this program's outcome for it was not recorded.` (below), and no failed line is printed), a refusal that failed no step (`'<step>': <error>`: the step it is about, with the engine's words, or the engine's words alone when the refusal names no step), the run ended (`the run has ended (<phase>)`; when an engine failure ended it and a step failed that `realm run resume` takes, it goes on `— to make '<step>' runnable again: realm run resume <id> --from <step>`, or, for several, `— to make a failed step runnable again: realm run resume <id> --from <one of: …>` with their names; when an operator ended it, it goes on `. An operator ended this run, with the reason "<reason>"; to run the work again, start a new run.`, never with `realm run resume`, which would erase that ending and its reason), a question opened (with the `realm run respond` command; when steps wait for its answer — ready by what they depend on, but held while a question is open — it names them first: `a question is open ('<step>' waits for its answer) — realm run respond …`, and the preview's `Nothing is owed to the engine:` line does the same), each step that cannot run (`'<step>' cannot run (<check>): <why>`, or `cannot run here (capability)` for a handler or adapter this program lacks, ending with its way out: `— load the missing extension, or run the step on a runner that has it`), work the engine still owes when the call stops, after a refusal for one (`the engine still owes '<step>' — to run it: realm run advance <id>`, or, for several, `the engine still owes '<a>', '<b>' — to run them until a step opens a question, fails or ends the run: realm run advance <id>`), agent steps ready (`an agent step is ready: '<step>' — drive it with realm agent --run-id <id> --provider <provider> --model <model>` for one, `agent steps are ready: '<a>', '<b>' — drive them with …` for several; put the provider and model you drive the run with in place of the two placeholders; the next line is the one `respond` prints after its commands, `If a realm workflow run or realm agent is still waiting on this run, it goes on by itself; the line above is for when none is.`, also after a preview line that names agent steps ready), each step another program holds (`'<step>' is in flight in another program — wait for it, or see realm run inspect <id>`), and otherwise `nothing is ready to run now`. A run the command completes gets no `Stopped:` line: the phase line says it. When the run ends during the call and has cleanup steps, one line per cleanup step that ending ran or left pending follows, just before the phase line, in the order the engine runs them, with the status the run's record holds for it: `finalizer '<name>': <status>` — `completed`, `failed` when its handler threw, or `pending` when this program could not run it (a `⚠` line above says why). A cleanup step that completed or failed when the run ended before, and `realm run resume` reopened the run, does not run again and gets no line. When the ending left cleanup steps `pending`, the next line names them and the command that runs them: `Cleanup step left pending: '<name>' — to run it with code that has its handler: realm run drain <id> --force` (`Cleanup steps left pending: '<a>', '<b>' — to run them with code that has their handlers: …` for several); the preview of a run that ended with cleanup steps pending prints the same line after its last line. While another drainer's lease on one of them has not passed, the line is `Cleanup step left pending: '<name>' — held by another drainer's lease until <time> (realm cannot tell whether it is still running) — after <time>: realm run drain <id> --force`, and a `⚠ finalizer '<name>' left pending — held by another drainer's lease until <time> (realm cannot tell whether it is still running)` line above says why it is `pending` (added after version 0.46.0). A step another process took while this one was about to run it is said as a fact, and the command goes on with what is left. The command prints `→ <step>` as it starts a step, before it claims it, so the losing program prints both lines, in this order — the second says another program took the step first, so it did not run here:

```text
→ process
• Step 'process' was taken by racer-a (from REALM_OPERATOR, via advance) at 2026-10-05T00:02:25.302Z; not run here.
```

When another program settles the step or takes it over (after `realm run reclaim <id> --step <step> --force` freed this program's claim, for one), or ends the run (`realm run abandon`, for one), while this one is running it, the outcome this program reached for the step is not recorded. The command says so, read from the run's record, never that the step failed, and goes on with what is left:

```text
→ process
• Step 'process' was taken by racer-b (from REALM_OPERATOR, via advance), and completed; this program's outcome for it was not recorded.
→ notify
Run 65d2afc8-2cb3-4401-808c-1d83a40bf989: phase 'completed'
```

The line ends `, and failed; …` when the other program's run of the step failed. While the other program still holds the step it reads `• Step '<step>' was taken by <program> at <time>; this program's outcome for it was not recorded.`, and when the run ended without the step settling, `• Step '<step>': the run ended (<phase>) before this program's outcome for it was recorded.` When no program holds the step and it has not settled, it reads `• Step '<step>': another process removed the claim this program held on it; this program's outcome for it was not recorded.`, and the step is owed again: the command goes on with it as with any owed step. A step whose outcome was not recorded did not fail here, also when the other program's run of it failed: the exit code is the one for what is left. When the record changed after the command read it and before it claimed the step (another program ended the run, or opened a question on another step), the step did not run here: no line says it did, and the `Stopped:` lines say what the record shows. Added after version 0.46.0.

When the engine can run nothing, the last preview line says why and nothing runs. It opens `Nothing is owed to the engine: <reason>` when nothing is owed (the run ended, a question is open, only agent steps are ready), and `The engine can run nothing now: <reason>` when steps are still owed to the engine but none can run here now (a step that cannot run, or a step in flight in another program). A reason that ends with a command ends the line there, with no full stop, so the command can be copied as printed; any other ends with a full stop. With more than one reason, the opening ends with its colon and each reason is a line of its own, indented two spaces (added after version 0.46.0, which joined them with `; ` and ended the line with a full stop). When the run stops on a step refused before its claim (an invalid `trust`, a failed precondition, an input its schema refuses) and nothing else is ready, the last line gives the way out — for a refused `trust` or precondition, correcting the workflow and registering it again is the fix, since the run picks up the corrected definition; for an input its schema refuses, the line names each such step's own way (a new run with params that fit, or a corrected `input_schema`; with `depends_on` the engine gives the step no input, so a corrected `input_schema`; for a step that builds its input with an `input_map`, a corrected `input_map` or `input_schema`, or a new run with params that fit when the key its schema refuses is built from `run.params`):

```text
The engine can run nothing now: 'compute' cannot run (input_schema): Invalid input for step 'compute': the input must have required property 'n'.
Run 507090b5-3b5b-4a6c-a814-3faa03404f95 stays open (phase 'running'): for 'compute', start a run with params that fit, or correct its input_schema and register the workflow again; then, after a fix, realm run advance 507090b5-3b5b-4a6c-a814-3faa03404f95 — or end it: realm run abandon 507090b5-3b5b-4a6c-a814-3faa03404f95
```

After a call that ran other steps, the same way out takes the place of the `Run <id>: phase '<phase>'` line. A step another program holds:

```text
In flight: 'process' is in flight, taken by crown (from REALM_OPERATOR, via advance) since 2026-10-04T22:26:13.994Z.
The engine can run nothing now: 'process' is in flight in another program — wait for it, or see realm run inspect 65d2afc8-2cb3-4401-808c-1d83a40bf989
```

Exit code 1 when a `Stopped:` line gives a step that failed or a refusal, or when a step cannot run, else 0. The steps run in this shell's environment (its secrets, its `.env`); two programs with the same code and different secrets look the same to the preview (#592).

## `resume`

```text
realm run resume <run-id> --from <step> [--force]
```

Takes a failed step off the run's list of failed steps and opens the run again. The run must be in `failed` or `abandoned`, and `<step>` must be listed under `Failed` by `realm run inspect`. Steps that completed stay completed.

| Flag            | Required | What it does                                                                                                  |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `--from <step>` | Yes      | The failed step to make runnable again.                                                                       |
| `--force`       | No       | Resumes even though another step of the run is marked in progress and Realm cannot tell how long it has been. |

```bash
realm run resume 8d4053bf-ceb4-4bde-bbde-3595715fd7c5 --from fetch
```

```text
Resumed run '8d4053bf-ceb4-4bde-bbde-3595715fd7c5': step 'fetch' re-enabled and run reset to 'running'.
Drive it with: realm agent --run-id 8d4053bf-ceb4-4bde-bbde-3595715fd7c5 --provider <provider> --model <model>
Add the other flags the run was driven with, such as --extensions-module or --project (realm run inspect 8d4053bf-ceb4-4bde-bbde-3595715fd7c5 shows the extension module the run loaded).
```

Fill in `<provider>` and `<model>` before you run the second line. `<model>` is the model to drive the run with. Give the flags you drove the run with: the model flags (`--provider-module`, or `--provider`, `--model`, `--base-url` and `--strict-base-url`) as you used them, and `--extensions-module`, `--project`, `--schema-retries`, `--llm-timeout` or `--mint-writer-nonce` if you used them. Realm does not record the model or most of those flags: `realm run inspect` shows the provider of a drive failure, and the extension module the run loaded under `Extension Identity`. A run started by `realm workflow run` was never driven by a model: name any provider and model you want. Version 0.45.0 prints the second line without `--provider <provider> --model <model>`, and no third line.

When the step that is ready again is one only the engine runs, one line names it (`the step` / `the steps`, and for more than one, `until a step opens a question, fails or ends the run,` before `without a model`) and the call that runs it without a model, and the `Drive it with:` lines are printed only when an agent step is ready too. Added after version 0.46.0, which prints the `Drive it with:` lines in this case too:

```text
Resumed run '8bc06d55-77fb-43f4-937f-8d166fad20cf': step 'a' re-enabled and run reset to 'running'.
To run the step the engine owes ('a') without a model: realm run advance 8bc06d55-77fb-43f4-937f-8d166fad20cf
```

When the step that is ready again cannot run — the workflow was registered again with a check the step fails — and nothing else can run, driving the run would only stop on that step: the step and the way out take the place of the `Drive it with:` lines. Added after version 0.46.0, which prints the `Drive it with:` lines in this case too:

```text
Resumed run '729eaab3-6203-46cb-9c5c-3714070723a8': step 'a' re-enabled and run reset to 'running'.
'a' cannot run (input_schema): Invalid input for step 'a': the input must have required property 'n'.
Run 729eaab3-6203-46cb-9c5c-3714070723a8 stays open (phase 'running'): for 'a', start a run with params that fit, or correct its input_schema and register the workflow again; then, after a fix, realm run advance 729eaab3-6203-46cb-9c5c-3714070723a8 — or end it: realm run abandon 729eaab3-6203-46cb-9c5c-3714070723a8
```

`resume` runs no step. Cleanup steps that had not yet run for the ended run are cancelled, and each is named on a line that starts with `⚠`.

**Exit code:** 0 if the run was resumed, otherwise 1:

| Case                                             | Message                                                                                                                                                                                 |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The step did not fail                            | `Step 'review' is not in failed_steps for run '00b33778-…'.`                                                                                                                            |
| The run is in another phase                      | `Run 4233aa3c-… is in phase 'completed', which is not resumable.`                                                                                                                       |
| The step is a cleanup step                       | `Step 'release' is a finalizer — finalizers cannot be resumed via --from. Use 'realm run drain cba9901c-…' or '--void release' instead.`                                                |
| The run was aborted                              | `Run <id> was aborted (step '<step>') — aborted runs are never resumable.`                                                                                                              |
| A step is in progress, and within its time limit | `Step '<step>' has a HEALTHY claim (deadline <time>) — a live runner is presumed on it. Resume refuses to disturb live work.`                                                           |
| A step is in progress, for an unknown time       | `Step '<step>' has an unknown-age claim (no reliable deadline) — its liveness cannot be verified. Re-run with --force to override (only after confirming the runner is actually dead).` |
| A cleanup step is being run now                  | `Run <id> has an active drain lease on finalizer '<name>' (expires <time>) — a drainer is executing NOW. …`                                                                             |

## `abandon`

```text
realm run abandon <run-id> [--reason <text>]
```

Ends an open run. Its phase becomes `abandoned`. The workflow's cleanup steps do not run.

| Flag              | What it does                                                             |
| ----------------- | ------------------------------------------------------------------------ |
| `--reason <text>` | Recorded as the run's cause. Default: `Abandoned via realm run abandon`. |

```bash
realm run abandon 00b33778-f504-4d65-93ca-6300441f41e7 --reason "upstream still down"
```

```text
Run '00b33778-f504-4d65-93ca-6300441f41e7' abandoned (phase: 'abandoned'). Reason: upstream still down.
To run the same work again: realm workflow run /srv/shop/sync --params '{"fail":true}' (the directory it was registered from) — a fresh run; this run's evidence stays at realm run inspect 00b33778-f504-4d65-93ca-6300441f41e7.
abandon is a kill — declared finalizers (if any) did NOT run and will not for this run. The graceful path is the workflow's own guard step (abort_unless), which runs them; there is no operator abort command.
```

The second line gives a command that starts a new run of the same workflow with the same parameters.

Abandoning a run that is already abandoned changes nothing, and the first line says so: `… Already abandoned (no change this call). …`

**Exit code:** 0 if the run is abandoned, otherwise 1:

| Case                         | Message                                                                                                                                                                                              |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The run is waiting at a gate | `Run '4233aa3c-…' is waiting on human gate 'review' (gate '70d76b3b-…'); answer it before abandoning. Answer it: realm run respond 4233aa3c-… --gate 70d76b3b-… --choice <one of: approve, reject>.` |
| The run ended in another way | `Run '08c61d5d-…' is already terminal (failed); cannot abandon a finished run.`                                                                                                                      |

## `cleanup`

```text
realm run cleanup --older-than <duration> [--dry-run]
```

Abandons every open run that has not changed for `<duration>`. It leaves runs that are waiting at a gate.

| Flag                      | Required | What it does                                             |
| ------------------------- | -------- | -------------------------------------------------------- |
| `--older-than <duration>` | Yes      | How long a run must have been idle.                      |
| `--dry-run`               | No       | Lists the runs that would be abandoned and changes none. |

```bash
realm run cleanup --older-than 7d --dry-run
```

```text
Would mark 4 run(s) as abandoned.
  • 0a8d87bf-b7fc-4983-96e2-3656718e231d
  • 3ebc1158-1d29-41b5-9ca5-df054a681b58
  • 6302fd4c-6a9f-4f17-b5ff-79574cb569b7
  • cba9901c-fa22-47dd-97e2-47439238d01f
abandon is a kill — declared finalizers (if any) did NOT run and will not for this run. The graceful path is the workflow's own guard step (abort_unless), which runs them; there is no operator abort command.
```

Without `--dry-run`, the first line reads `Marked 4 run(s) as abandoned.` A run it abandons shows `Sealed by: cleanup_sweep` and `Cause: Marked abandoned by realm cleanup` in `realm run inspect`.

**Exit code:** 0, also when no run matches. 1 for a duration that cannot be read.

## `reclaim`

```text
realm run reclaim <run-id>
realm run reclaim <run-id> --step <name> --force
realm run reclaim --all [--workflow <id>] [--older-than <duration>] [--force]
```

When a step starts, Realm marks it in progress and, where it can, records a time by which the step must have finished. If the process running the step dies, the mark stays and the run cannot move. `reclaim` reports such marks and, with `--force`, removes one so that the step can run again. The step's work may then happen twice.

| Flag                      | What it does                                                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `--step <name>`           | The step to free. Needs `--force`.                                                                                        |
| `--force`                 | Frees the step, or with `--all` every selected step.                                                                      |
| `--all`                   | Selects, across all runs, every step that is past its time, is declared `idempotent: true`, and is not waiting at a gate. |
| `--workflow <id>`         | With `--all`: only runs of this workflow.                                                                                 |
| `--older-than <duration>` | With `--all`: only steps that are past their time by at least this long.                                                  |

With a run ID alone, it lists the steps in progress and the state of each:

```bash
realm run reclaim 70f9528e-4f45-4806-8690-c4e92a31760d
```

```text
Run '70f9528e-4f45-4806-8690-c4e92a31760d' (running) — in-progress claims:
  • fetch: claim_stale  (deadline: 2026-10-01T23:15:36.957Z)
      reclaim: realm run reclaim 70f9528e-4f45-4806-8690-c4e92a31760d --step fetch --force

Reclaim re-drives a step (at-least-once — side effects may repeat). Use --step <name> --force to act on a specific claim, or --all for batch auto-reclaim.
```

| State               | Meaning                                                                                                    |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `healthy`           | The step's time has not passed. Something is presumed to be running it.                                    |
| `claim_stale`       | The step's time has passed.                                                                                |
| `claim_unknown_age` | No time was recorded. This is so for agent steps, and for every step of a workflow that has cleanup steps. |

The time is the step's `timeout_seconds` across all its attempts plus one minute, and never less than 15 minutes after the step started.

A step that is waiting at a gate is listed with `open gate — resolve via 'realm run respond <run-id>', not reclaim.`

With `--step` and `--force`:

```text
⚠ Reclaiming 'fetch' on run '70f9528e-4f45-4806-8690-c4e92a31760d' — this re-drives the step; its side effects may repeat.
Reclaimed 'fetch' (was claim_stale). It is eligible again — the next driver will re-drive it.
```

`reclaim` runs no step. The freed step runs when a driver next calls the run. The run's record keeps an entry for the step that says it was reclaimed, with the state and the time it had.

Freeing a step that is still `healthy` is allowed, with a second warning first: `⚠ 'fetch' currently has a HEALTHY claim (a live runner is presumed on it). --force will override it and may double-drive live work.`

With `--all`:

```text
2 claim(s) WOULD be auto-reclaimed (idempotent ∧ past-deadline ∧ non-gated):
  • 70f9528e-4f45-4806-8690-c4e92a31760d / fetch  (deadline 2026-10-01T23:15:36.957Z, 0m past)
  • 7252133d-1033-4300-8ddb-ff949f646a79 / fetch  (deadline 2026-10-01T23:15:34.551Z, 0m past)

Re-run with --force to reclaim them. WARNING: each re-executes its handler at-least-once with NO per-step human judgment — enable only for genuinely idempotent handlers.
```

With `--all --force`:

```text
⚠ Auto-reclaiming 1 claim(s); each re-executes its handler — side effects may repeat.
  ✓ 7252133d-1033-4300-8ddb-ff949f646a79: Reclaimed 'fetch' (was claim_stale). It is eligible again — the next driver will re-drive it.
Reclaimed 1/1 selected claim(s).
```

When nothing is selected, it prints `No auto-reclaimable claims found (idempotent ∧ concrete-past-deadline ∧ non-gated).` A run with no step in progress gets `Run '<id>' (running) has no in-progress claims. Nothing to reclaim.`, and a run that has ended gets `Run '<id>' is terminal (completed); it has no reclaimable claim.`

**Exit code:** 0, also when there is nothing to free. 1 for a combination that cannot be used:

```text
Provide a <run-id> to inspect/reclaim, or use --all for batch mode.
Refusing to reclaim 'fetch' without --force. Re-run with --force to re-drive it (the step re-executes; its side effects may repeat).
--force requires --step <name> (or use --all for batch). Run without arguments to see a dry-run.
--workflow and --older-than are only valid with --all.
Cannot combine a <run-id> with --all. Use one or the other.
--step is a per-run flag; it cannot be combined with --all.
```

## `drain`

```text
realm run drain <run-id> [--force] [--expired] [--project <dir>] [--extensions-module <path>]
realm run drain <run-id> --void <finalizer>
realm run drain --all [--force] [--expired]
```

A workflow's cleanup steps, the steps with `execution: finalizer`, run after the run has ended. If the process stops before they finish, the run stays ended and still owes them. `drain` reports the cleanup steps a run owes and, with `--force`, runs them.

| Flag                         | What it does                                                                                                   |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--force`                    | Runs the cleanup steps.                                                                                        |
| `--all`                      | Every run that owes a cleanup step that can be run now.                                                        |
| `--void <finalizer>`         | Cancels one owed cleanup step without running it. Acts at once, without `--force`.                             |
| `--expired`                  | Also carries out gates whose time has passed. See [Gates](https://realmengine.dev/docs/reference/workflow/gates.md#what-happens-at-expiry).        |
| `--project <dir>`            | The project whose `realm.yaml` applies if the workflow has no project of its own. Default: the current folder. |
| `--extensions-module <path>` | Loads this code file in place of the files named by the workflow's `extensions`.                               |

Without `--force`, it reports:

```text
Run '64e1c3a6-5882-4955-a796-a03355fe97f4' (completed) — pending finalizers, rank order:
  • [0] release: actionable — would lease and run on --force, if its handler resolves on this surface

Re-run with --force to actually drain. Use --void <finalizer> to void one instead.
```

A cleanup step that another process took less than its lease time ago is reported as held. The lease here was 30 seconds:

```text
  • [0] release: lease held (expires 2026-10-01T23:01:37.538Z) — a drainer is executing NOW
```

With `--force` while that lease has not passed, the pass stops at that step and runs nothing more: it prints `⚠ finalizer '<name>' left pending — held by another drainer's lease until <time> (realm cannot tell whether it is still running)`, then `Cleanup step left pending: '<name>' — held by another drainer's lease until <time> (realm cannot tell whether it is still running) — after <time>: realm run drain <id> --force`, and exits 1 — never `has no pending finalizers`. After that time the command runs it. Added after version 0.46.0.

With `--force`:

```text
Drained run '64e1c3a6-5882-4955-a796-a03355fe97f4'.
```

With `--void`:

```text
finalizer 'release' voided by operator — may have executed without a recorded mark (its lease had already expired)
```

With `--all`, and then with `--all --force`:

```text
3 run(s) WOULD be drained:
  • 1bdb1307-af8b-4016-8189-f054621411b9
  • 41a348b2-e48e-48ee-8622-21df7135688e
  • 64e1c3a6-5882-4955-a796-a03355fe97f4

Re-run with --force to actually drain them.
```

```text
  ✓ 1bdb1307-af8b-4016-8189-f054621411b9: drained
Drained 1/1 run(s).
```

With `--expired`, a gate whose time has passed is reported first, with the choice it would be settled with. If the settled choice would make a guard step ready, the report says what the guard would then do: `would pass`, `would pass and complete the run`, `would then abort the run (<reason>)` or `would then fail the run (<reason>)`. Nothing is written:

```text
Run '0e0ace7e-e57c-400c-a63a-eaf685688d19': gate expired 18s ago — would enact settle_default 'hold'; guard 'only_if_shipping' would then abort the run (The order was held.) on --force.
Run '230b0939-9c61-40e4-8b6f-910594a81e92': gate expired 17s ago — would enact settle_default 'ship'; guard 'only_if_shipping' would pass on --force.
```

The report leaves the guard out when the run's registered workflow cannot be read.

With `--expired --force`, the gate is carried out. The same write decides the guard, and its lines follow. Then the run's cleanup steps are drained as usual:

```text
✓ gate enacted (settle_default 'hold').
Guard step 'only_if_shipping' aborted the run.
Reason: The order was held.
Run '0e0ace7e-e57c-400c-a63a-eaf685688d19' has no pending finalizers. Nothing to drain.
```

```text
✓ gate enacted (settle_default 'ship').
Guard step 'only_if_shipping' passed.
Run '230b0939-9c61-40e4-8b6f-910594a81e92' is not terminal (phase: 'running') — nothing further to drain.
An agent step is ready: 'ship' — drive it with realm agent --run-id 230b0939-9c61-40e4-8b6f-910594a81e92 --provider <provider> --model <model>
```

When the enacted gate leaves `auto` steps only the engine runs, one more line names them. Added after version 0.46.0:

```text
✓ gate enacted (settle_default 'approve').
Run 'daeede5e-c0dd-4b88-9caf-6efa089902dd' is not terminal (phase: 'running') — nothing further to drain.
To run the step the engine owes ('after'): realm run advance daeede5e-c0dd-4b88-9caf-6efa089902dd
```

When it leaves nothing that can run from here, the steps that cannot run and the way out follow instead. Added after version 0.46.0:

```text
✓ gate enacted (settle_default 'approve').
Run '1d703406-777f-4bc6-a6bc-6058b4d8f490' is not terminal (phase: 'running') — nothing further to drain.
'compute' cannot run (input_schema): Invalid input for step 'compute': the input must have required property 'n'.
Run 1d703406-777f-4bc6-a6bc-6058b4d8f490 stays open (phase 'running'): for 'compute', the engine gives it no input, so correct its input_schema and register the workflow again; then, after a fix, realm run advance 1d703406-777f-4bc6-a6bc-6058b4d8f490 — or end it: realm run abandon 1d703406-777f-4bc6-a6bc-6058b4d8f490
```

When it leaves no work for the engine and nothing that cannot run, but an agent step ready or a step in flight in another program, the line `realm run advance` prints for it follows (the guard that passed above leaves the agent step `ship` ready). Added after version 0.46.0, which prints nothing after `nothing further to drain.` there:

```text
✓ gate enacted (settle_default 'approve').
Run '7d2e9b14-5a3c-4f81-b0e6-93c4a1f2d857' is not terminal (phase: 'running') — nothing further to drain.
'pack' is in flight in another program — wait for it, or see realm run inspect 7d2e9b14-5a3c-4f81-b0e6-93c4a1f2d857
```

With `--all --expired`, the list names what each gate declared, without the choice or the guard. With `--force`, what each guard did is printed under its run:

```text
2 run(s) WOULD be drained:
  • 962cb7f0-a894-4592-aadc-a4907ef14c9c: gate expired 41s ago — would enact settle_default
  • ce8296d1-c5d3-4ea6-b62c-7dc0f795fb09: gate expired 40s ago — would enact settle_default

Re-run with --force to actually drain them.
```

```text
  ✓ 962cb7f0-a894-4592-aadc-a4907ef14c9c: gate enacted
    Guard step 'only_if_shipping' passed.
  ✓ ce8296d1-c5d3-4ea6-b62c-7dc0f795fb09: gate enacted
    Guard step 'only_if_shipping' aborted the run.
    Reason: The order was held.
Drained 2/2 run(s).
```

When there is nothing to do, it prints one of:

```text
Run '03431f4f-7b71-4ad0-98b1-f1d51cc5c4c8' has no pending finalizers. Nothing to drain.
Run 'cba9901c-fa22-47dd-97e2-47439238d01f' is not terminal (phase: 'running') — nothing to drain. To end the run: realm run abandon cba9901c-fa22-47dd-97e2-47439238d01f
Run 'daeede5e-c0dd-4b88-9caf-6efa089902dd' is not terminal (phase: 'running') — nothing to drain. To run the step the engine owes ('after'): realm run advance daeede5e-c0dd-4b88-9caf-6efa089902dd
To end the run instead: realm run abandon daeede5e-c0dd-4b88-9caf-6efa089902dd
Run '573ff99d-44fc-42c9-98e8-c394fed45e6e' is not terminal (phase: 'running') — nothing to drain. 'compute' cannot run (input_schema): Invalid input for step 'compute': the input must have required property 'n'. Run 573ff99d-44fc-42c9-98e8-c394fed45e6e stays open (phase 'running'): for 'compute', start a run with params that fit, or correct its input_schema and register the workflow again; then, after a fix, realm run advance 573ff99d-44fc-42c9-98e8-c394fed45e6e — or end it: realm run abandon 573ff99d-44fc-42c9-98e8-c394fed45e6e
Run 'e1a7c2b4-6d90-4f3e-8b25-0c7d9e1f4a68' is not terminal (phase: 'running') — nothing to drain. An agent step is ready: 'write' — drive it with realm agent --run-id e1a7c2b4-6d90-4f3e-8b25-0c7d9e1f4a68 --provider <provider> --model <model>
To end the run instead: realm run abandon e1a7c2b4-6d90-4f3e-8b25-0c7d9e1f4a68
Run '5b3f90de-2c47-4a18-9e6d-f81a0b7c3d25' is not terminal (phase: 'running') — nothing to drain. 'fetch' is in flight in another program — wait for it, or see realm run inspect 5b3f90de-2c47-4a18-9e6d-f81a0b7c3d25
To end the run instead: realm run abandon 5b3f90de-2c47-4a18-9e6d-f81a0b7c3d25
Run '22efc6a7-01f8-4256-9d2f-74621b621d28' is not terminal (phase: 'gate_waiting') — nothing to drain. To go on, answer its question first: realm run respond 22efc6a7-01f8-4256-9d2f-74621b621d28 --gate 817f3921-6ddd-4bda-9506-762892ae37e7 --choice <one of: approve, reject>
The answer can end the run by itself; if it does not, realm run respond names what the run owes next, and realm run abandon 22efc6a7-01f8-4256-9d2f-74621b621d28 ends it.
Run '94bf33c8-3933-47c4-ad50-556d0b298e6c' is not terminal (phase: 'gate_waiting') — nothing to drain. Its gate expired 25s ago. To see what the expiry will do: realm run drain 94bf33c8-3933-47c4-ad50-556d0b298e6c --expired
To carry it out: realm run drain 94bf33c8-3933-47c4-ad50-556d0b298e6c --expired --force
No runs with an actionable pending finalizer.
```

The runs after the second — the work the engine owes, a step that cannot run and the way out, an agent step that is ready, a step in flight in another program, each with the way out after it — were added after version 0.46.0, which prints the second line's `To end the run: realm run abandon <id>` for every run in `running`. The second line is now for a run with none of these. A command ends its line: where the way out follows it, it is the next line (added after version 0.46.0, which printed them on one line, the command followed by a full stop). At a question, the second line says that after the answer `realm run respond` names what the run owes next (added after version 0.46.0, which named only `realm run abandon` for a run still open after the answer).

**Exit code:** 0 if every cleanup step it tried ran, and when there is nothing to do. 1 if a cleanup step is left owed after `--force`, if `--force` prints one of the seven `is not terminal … nothing to drain` lines above, or for one of:

```text
Provide a <run-id>, or use --all for batch mode.
Finalizer 'release' is 'voided', not 'pending' — nothing to void.
No finalizer ledger entry named 'nope' on run '41a348b2-e48e-48ee-8622-21df7135688e'.
Finalizer 'release' has an active drain lease (expires 2026-10-01T23:02:59.535Z) — a drainer is executing NOW. Wait for the lease to expire (bounded — leases are clamped to 300s) and retry. Not force-bypassable.
```

## `purge`

```text
realm run purge <run-id> [--force]
realm run purge --older-than <duration> [--workflow <id>] [--force]
```

Deletes finished runs, with their refused answers and trace entries. A deleted run cannot be brought back. Runs that have not ended are never deleted.

| Flag                      | What it does                                                   |
| ------------------------- | -------------------------------------------------------------- |
| `--force`                 | Deletes. Without it, `purge` lists what it would delete.       |
| `--older-than <duration>` | Selects every finished run that has not changed for this long. |
| `--workflow <id>`         | With `--older-than`: only runs of this workflow.               |

```bash
realm run purge --older-than 30d
```

```text
3 run(s) WOULD be purged (7.2 KB to free):
  • 08c61d5d-8db8-4556-87e4-0ba4cc76dc6c  (failed, 2.4 KB)
  • 5ef64237-9f2d-4624-84c8-9686feb38998  (completed, 2.4 KB)
  • d0375dc9-35f5-462b-b12c-f145b404f276  (abandoned, 2.5 KB)

1 of 3 selected run(s) are resumable via 'realm run resume' — purging would destroy that path permanently.
Re-run with --force to actually delete.
```

With `--force`:

```text
Purged 2/3 run(s) (4.9 KB freed, store-reported). 0 already gone, 1 blocked, 0 failed.
  ⚠ 5ef64237-9f2d-4624-84c8-9686feb38998: drain_pending
1 of 3 selected run(s) were resumable via 'realm run resume' — purging has destroyed that path for them.
```

| Count          | Means                                                                                                         |
| -------------- | ------------------------------------------------------------------------------------------------------------- |
| `Purged`       | Runs deleted, of the runs selected.                                                                           |
| `already gone` | Runs that something else deleted first.                                                                       |
| `blocked`      | Runs that were left in place. `drain_pending` means the run still owes a cleanup step; see [`drain`](#drain). |
| `failed`       | Runs that could not be deleted because of an error.                                                           |

The line about `realm run resume` counts the selected runs that had a failed step `resume` could have made runnable again: a failed cleanup step is not counted, since `resume --from` refuses it. A run whose workflow cannot be read is counted when it has any failed step: `resume` refuses it until the workflow is registered again, and only the workflow tells a cleanup step from another.

Given a run ID in place of `--older-than`, `purge` works on that one run:

```text
1 run(s) WOULD be purged (2.7 KB to free):
  • 03431f4f-7b71-4ad0-98b1-f1d51cc5c4c8  (completed, 2.7 KB)

None of the 1 selected run(s) are resumable via 'realm run resume'.
Re-run with --force to actually delete.
```

When no run is old enough, it prints `No eligible runs found to purge.`

**Exit code:** 0, also when a run was blocked or nothing matched. 1 if a run could not be deleted because of an error, or for one of:

```text
Provide a <run-id> to purge, or use --older-than for batch mode.
Cannot combine a <run-id> with --older-than. Use one or the other.
Refusing to purge 'f78b61ba-3464-4802-8bc0-03fcaaeebb8d': not terminal (phase: 'gate_waiting').
```

## `gc`

```text
realm run gc --older-than <duration> [--heal] [--force]
realm run gc --heal [--force]
```

Removes two kinds of file that a crash can leave in `~/.realm/runs/`: temporary files from a write that was cut off, and trace entries whose run no longer exists. With `--heal`, it also corrects run records whose stored phase differs from the phase their contents give.

| Flag                      | What it does                                                                                                      |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `--older-than <duration>` | Only files at least this old. The least it accepts is `1h`. Required unless `--heal` is the only thing asked for. |
| `--force`                 | Deletes and rewrites. Without it, `gc` lists what it would do.                                                    |
| `--heal`                  | Also corrects stored phases. No age limit applies to this part.                                                   |

```bash
realm run gc --older-than 1d
```

```text
1 orphaned .tmp file(s) WOULD be reaped (2.4 KB to free):
  • ~/.realm/runs/08c61d5d-8db8-4556-87e4-0ba4cc76dc6c.json.4242.9f3c2a1b.tmp

Re-run with --force to actually delete.

1 run-less orphaned artifact(s) WOULD be reaped:
  • ~/.realm/runs/trace-buffer-fbe2f08b-b5e2-435b-ac41-77b0be623b33-d29yaw.jsonl  (run fbe2f08b-b5e2-435b-ac41-77b0be623b33)
Re-run with --force to actually delete.

gc does NOT yet reap orphaned .lock dirs (deferred — issue #164). Their presence in runsDir is expected and not a sign gc is broken.
```

On version 0.45.0 the part after the process id is a counter instead of 8 random characters: `….json.4242.1.tmp`. gc removes both forms.

With `--force`:

```text
Reaped 1 orphaned .tmp file(s) (2.4 KB freed). 0 already gone, 0 failed.

Reaped 1 run-less orphaned artifact(s). 0 already gone, 0 failed.
  • ~/.realm/runs/trace-buffer-fbe2f08b-b5e2-435b-ac41-77b0be623b33-d29yaw.jsonl  (run fbe2f08b-b5e2-435b-ac41-77b0be623b33)
```

Both forms end with the same note about lock folders. On a store with nothing to remove:

```text
No orphaned .tmp files found to reap.

No run-less orphaned WAL/sidecar artifacts found to reap.

gc does NOT yet reap orphaned .lock dirs (deferred — issue #164). Their presence in runsDir is expected and not a sign gc is broken.
```

With `--heal`, on a store with nothing to correct:

```text
No stale-phase records found to heal.
```

Correcting a record sets its time of last change to now. `realm run purge --older-than` and `realm run cleanup --older-than` read that time.

**Exit code:** 0. 1 if a file could not be read or deleted, or for:

```text
--older-than must be at least 1h (got '30m'). gc refuses to reap crash residue younger than that, even with --force.
```

## `reconcile`

```text
realm run reconcile [--workflow <id>] [--dry-run]
```

Realm keeps one small file for each idempotency key, in `~/.realm/runs/keys/`, that names the run holding the key. `reconcile` writes any of these files that is missing or out of date, from the runs themselves.

| Flag              | What it does                                      |
| ----------------- | ------------------------------------------------- |
| `--workflow <id>` | Only runs of this workflow.                       |
| `--dry-run`       | Reports what would be written and writes nothing. |

```bash
realm run reconcile --dry-run
```

```text
Would write 1 pointer(s) across 1 key group(s); 0 already current.
```

Without `--dry-run`, the line starts with `Wrote`. Run again, it prints `Wrote 0 pointer(s) across 1 key group(s); 1 already current.` A key group is one key of one workflow. See [Idempotency and batches](https://realmengine.dev/docs/guides/idempotency-and-batches.md) for a store where two runs hold one key.

**Exit code:** 0.

## `migrate`

```text
realm run migrate --stamp-seals [--force] [--detailed-exitcode]
```

Since version 0.39, a run that ends records what ended it, which `realm run inspect` prints as `Sealed by`. `migrate --stamp-seals` works out that value for finished runs written by older versions and stores it. It does not change a run's time of last change.

| Flag                  | Required | What it does                                                                                                                |
| --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `--stamp-seals`       | Yes      | The only kind of migration.                                                                                                 |
| `--force`             | No       | Writes. Without it, `migrate` lists what it would write.                                                                    |
| `--detailed-exitcode` | No       | Exits with 2 if a finished run is left whose ending cannot be worked out, or whose record disagrees with its stored ending. |

On two runs written by version 0.38:

```bash
realm run migrate --stamp-seals
```

```text
2 run(s) WOULD be stamped:
  • 9a45ca3c-2df1-4daf-ac77-832344422fed: complete (phase completed)
  • c9334380-2d09-463e-96c1-2a98f98a66c9: abandon_requested (phase abandoned)
Re-run with --force to actually stamp.
Residue: 2 terminal run(s) still without a recorded seal arm.
Run `realm run migrate --stamp-seals` BEFORE `realm run gc --heal` after upgrading across #367 if retention clocks matter: heal rewrites the same records and resets updated_at, which this command deliberately preserves.
```

With `--force`, the first lines become:

```text
Stamped 2 run(s) with their seal arm.
  • 9a45ca3c-2df1-4daf-ac77-832344422fed: complete (phase completed)
  • c9334380-2d09-463e-96c1-2a98f98a66c9: abandon_requested (phase abandoned)
Residue: 0 terminal run(s) still without a recorded seal arm.
```

A run stamped in this way shows `Sealed by: complete (recovered by classifier)` in `realm run inspect`.

When every finished run has the value already:

```text
Nothing would be stamped — every terminal run already carries its seal arm.
3 run(s) already stamped, and their arms agree with the record.
Residue: 0 terminal run(s) still without a recorded seal arm.
```

**Exit code:** 0. 1 if a write failed, or if `--stamp-seals` was not given. 2 only with `--detailed-exitcode`, as described above.

## See also

- [Operate runs](https://realmengine.dev/docs/guides/operate-runs.md)
- [`realm run`: commands that read](https://realmengine.dev/docs/reference/cli/realm-run-reading.md)
- [Handle failure](https://realmengine.dev/docs/guides/handle-failure.md)
- [Upgrade Realm](https://realmengine.dev/docs/guides/upgrade.md)
