# Undo a run

Preview an undo, approve it and check the result with basalt revert. Anything your team or your app changed since stays as it is.

Use this page when an agent's run went wrong and you want its rows back. Undo restores what the agent changed. It leaves alone any row your team or your app changed since. For why, see [Undo](/concepts/undo/).

You need the agent's name and the run id. To find the run id, ask the agent for its runs (it has `list_runs`), or read the record with [`basalt log`](/guides/see-what-it-did/). The examples use the agent `demo_bot` and the run id `RUN_ID`.

## 1. Preview the undo

Add `--dry-run` to see the plan. Nothing is changed and nothing needs approving.

```sh
basalt revert --agent demo_bot --run RUN_ID --dry-run
```

```text
restore    basalt_demo.leads {"id": 1}: score: 47 -> 37, status: "QUALIFIED" -> "NEW"
restore    basalt_demo.leads {"id": 3}: score: 21 -> 11, status: "QUALIFIED" -> "NEW"
skip       basalt_demo.leads {"id": 2}: CHANGED_AFTER
basalt: 2 rows to restore, 1 left as they are (people win)
basalt: dry run, nothing was reverted
```

`restore` is a row that goes back to the values it had before the agent, shown as `column: now -> restored`. `skip` is a row the undo leaves alone, with the reason next to it. Here lead 2 was changed after the agent (`CHANGED_AFTER`), so it stays.

## 2. Approve the undo

Run the command without `--dry-run`. Add `--open` to open the undo link in your browser.

```sh
basalt revert --agent demo_bot --run RUN_ID --open
```

The command prints the plan and a link, then waits for you. Nothing is undone yet. Open the link, check the rows and approve the undo. The command finishes by itself, with its last line:

```text
restore    basalt_demo.leads {"id": 1}: score: 47 -> 37, status: "QUALIFIED" -> "NEW"
restore    basalt_demo.leads {"id": 3}: score: 21 -> 11, status: "QUALIFIED" -> "NEW"
skip       basalt_demo.leads {"id": 2}: CHANGED_AFTER
basalt: 2 rows to restore, 1 left as they are (people win)
basalt: revert REVERT_ID expires 2026-11-15 08:21 UTC. Open this link on a device with your passkey, check the rows, and sign:
http://localhost:8787/reverts/REVERT_ID#s=...
basalt: waiting for your signature on the revert page (or paste the code it shows here)...
basalt: reverted: 2 rows restored, 1 left as they are
```

Without `--open` the command prints the link instead. Copy it into your browser. If the page cannot reach the command, it shows a code. Paste the code into the terminal and the undo applies the same way.

The command exits with `0` even when some rows were kept. A partial undo is a normal result. For how approving works, and what to do if you lose your passkey, see [Passkeys](/guides/passkeys/).

## 3. Check the result

The undo is recorded as a new change that names you. It belongs to no run, so `basalt log --run RUN_ID` does not show it. Read the agent's record, newest first:

```sh
basalt log --agent demo_bot
```

The undo is the newest change. The record shows what the undo did, not what the table holds now. For the current values, query the table:

```sql
SELECT id, status, score FROM basalt_demo.leads WHERE id <= 3 ORDER BY id;
```

The restored rows hold their old values. The kept rows hold the newer values. Nothing in the ledger was edited.

## Undo less, or more

`basalt revert` takes one scope.

| Flag          | What it undoes                |
| ------------- | ----------------------------- |
| `--run ID`    | Everything one run changed.   |
| `--change ID` | One change inside a run.      |
| `--task ID`   | Changes that share a task id. |

`--intent TEXT` is not a scope. It sets the reason for the undo, kept in the log. Without it the reason is `Revert run RUN_ID`, or the same for the change or task.

`--agent` names the agent whose work is undone, and `basalt revert` prepares only that agent's own work. For `--task` that means its own lines in the task. If other agents changed rows in the task, you can widen the undo on the signing page. The command never widens it by itself.

To drop a prepared undo, run `basalt revert --agent demo_bot --reject REVERT_ID`. It is the same as pressing Reject on the signing page. Add `--note TEXT` to say why.

An undo can be undone. Run `basalt revert --change ID` with the change id of the undo, and the values the first undo put back are brought back again.

## Ask your agent to do it

You can ask the agent: "Undo the run that qualified this week's leads. Keep anything a person changed." The agent finds the run, previews the undo against today's data, and tells you what it would restore and what it would keep. If you agree, it prepares the undo and shows you one link.

The agent cannot approve its own undo. Only you can. Open the link, check the rows and approve. The agent waits about 90 seconds for you. If you take longer, it tells you the undo is waiting. Approve when you are ready, then tell the agent.

An agent can ask to undo only its own work: its changes, its runs, or its own part of a task. If another agent's run went wrong, ask that agent, or run `basalt revert` yourself.

On an engine your bot runs on its own computer, ask the bot. See [A bot on its own computer](/guides/bot-own-computer/).

## Large and late undos

- **More than 1000 rows.** The command prints the size of the plan and the rows the link carries, not every row. You approve once, and the agent applies the plan in batches. `basalt revert --agent demo_bot --status REVERT_ID` prints the progress.
- **An hour to approve.** A prepared undo expires after 1 hour if nobody approves it. Run the command again to prepare a new one. A large plan that you approved must be finished within 24 hours.
- **Apply later.** `basalt revert --agent demo_bot --apply CODE` applies an undo that was prepared earlier, within its hour.

## If something goes wrong

- `NOTHING_TO_REVERT`: every row in the scope would be kept, or the run wrote no rows. A run that only read data has nothing to restore.
- The plan keeps a row you expected to restore: someone wrote it after the agent. [Why a row is skipped](/concepts/undo/#why-a-row-is-skipped) lists each reason.
- `NOT_FOUND`: the run id does not exist, or is not this agent's. Check the id with `basalt log` and the name after `--agent`.
- `QUERY_TIMEOUT` while an agent prepares an undo: the plan took over 100 seconds to prepare. Run `basalt revert` yourself, first with `--dry-run`, then with `--open`. See [When something is refused](/guides/refused/#timeout).
- The engine is not running: the command prints `basalt: the engine is not running; start it with ...` and the command to start it. Run that command, then run `basalt revert` again.

## Next

- [Undo](/concepts/undo/): why a row is kept, the 30-day window and what undo covers.
- [See what it did](/guides/see-what-it-did/): read the record of a run.
- [Passkeys](/guides/passkeys/): add, check and remove passkeys.
