# revert

> Release: 0.11.0 · Last changed: 2026-11-09 · Source: https://github.com/KeiaiLab/SwarmOps/blob/stable/docs/reference/mcp-tools/revert.md

Takes back changeset (with since, the first-parent range after since) on target, main by default, by landing its inverse: what the target gained since stays, and a later change to the same lines is E\_CONFLICT naming the changeset that made it, nothing recorded. Give a reason; the landed changeset records it and the range (show's reverts and revert\_reason). Give idempotency\_key to make a retry safe, and expect\_target as a full 64-hex id.

```yaml
title: "revert"
description: "Revert a change."
since: "0.11.0"
mutates: true
identity_required: true
cli_command: "swarmops revert"
envelope_op: "revert"
request_type: "RevertReq"
annotations: {"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":false}
max_result_chars: null
```

## Input schema

`arguments` is a `RevertReq`: `swarmops schema RevertReq` prints the type. A field the type does not know is ignored.

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `changeset` | string | yes |  | The range's last changeset: on the target's first-parent chain (else `E_INVALID_ARG`, `details.reason` `not_on_target`). A revision as the caller wrote it, for the store to resolve: a ref name (`main`, `release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits, or a ref's earlier value: `<ref>@{N}` the value it held N moves before its current one (`@{0}` is the current value), `<ref>@{at=<unix seconds>}` the value set by its newest move recorded at or before that instant (the writer's clock, moves read in journal order); optionally followed by `~N` for the N-th first-parent ancestor. |
| `expect_target` | string or null | no |  | Publish only while the target names this changeset, as a land's. |
| `idempotency_key` | string or null | no |  | Makes a retry safe, as a land's key: the same revert again replays it; the same key for another range is `E_INVALID_ARG`. |
| `max_attempts` | integer | no | `8` | How many times to try to publish before failing with `E_CAS_RETRY_EXHAUSTED`: 1 to 32. |
| `max_conflict_bytes` | integer | no | `32768` | The most bytes of hunk text an `E_CONFLICT` report carries, as a land's. |
| `message` | string or null | no |  | The landed changeset's message. When omitted, `Revert <12 hex digits of changeset>: <its title>` (a range: `Revert <n> changesets <since>..<changeset>`), a blank line and the reason. |
| `reason` | string | yes |  | Why: 1 to 4,096 bytes, recorded as the landed changeset's `revert_reason` metadata, and the default message's body. |
| `since` | string or null | no |  | Where the range starts, exclusive: a first-parent ancestor of `changeset`. When omitted, its first parent, so the range is `changeset` alone; a root's is the empty tree. At most 256 changesets (`E_TOO_LARGE`). |
| `target` | string | no | `"main"` | The ref to land the revert on: `main` or `release/<name>`, which must exist. |

### JSON Schema

As `tools/list` gives it: each `$ref` replaced by its definition, so that it stands alone.

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "`revert` (CLI `revert`): takes back the first-parent range `(since, changeset]` of `target`\nby a land. The land commits the inverse change on `changeset`, every path the range touched\nset to what `since` held there, and lands it from `changeset`: what\nthe target gained since stays, a later change to the same lines is `E_CONFLICT` naming the\nchangeset that made it, and nothing is recorded then. A range whose changes cancel out, or\none the target took back already, is `E_NOTHING_TO_LAND`. A range whose changesets include\nmerges is taken back against their first parents.",
  "properties": {
    "changeset": {
      "description": "The range's last changeset: on the target's first-parent chain (else `E_INVALID_ARG`,\n`details.reason` `not_on_target`). A revision as the caller wrote it, for the store to resolve: a ref name (`main`,\n`release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits, or a\nref's earlier value: `<ref>@{N}` the value it held N moves before its current one (`@{0}`\nis the current value), `<ref>@{at=<unix seconds>}` the value set by its newest move\nrecorded at or before that instant (the writer's clock, moves read in journal order);\noptionally followed by `~N` for the N-th first-parent ancestor.",
      "type": "string"
    },
    "expect_target": {
      "anyOf": [
        {
          "pattern": "^[0-9a-fA-F]{64}$",
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "Publish only while the target names this changeset, as a land's."
    },
    "idempotency_key": {
      "description": "Makes a retry safe, as a land's key: the same revert again replays it; the same key for\nanother range is `E_INVALID_ARG`.",
      "type": [
        "string",
        "null"
      ]
    },
    "max_attempts": {
      "default": 8,
      "description": "How many times to try to publish before failing with `E_CAS_RETRY_EXHAUSTED`: 1 to 32.",
      "format": "uint32",
      "maximum": 32,
      "minimum": 1,
      "type": "integer"
    },
    "max_conflict_bytes": {
      "default": 32768,
      "description": "The most bytes of hunk text an `E_CONFLICT` report carries, as a land's.",
      "format": "uint64",
      "minimum": 0,
      "type": "integer"
    },
    "message": {
      "description": "The landed changeset's message. When omitted, `Revert <12 hex digits of changeset>:\n<its title>` (a range: `Revert <n> changesets <since>..<changeset>`), a blank line and\nthe reason.",
      "type": [
        "string",
        "null"
      ]
    },
    "reason": {
      "description": "Why: 1 to 4,096 bytes, recorded as the landed changeset's `revert_reason` metadata, and\nthe default message's body.",
      "type": "string"
    },
    "since": {
      "anyOf": [
        {
          "description": "A revision as the caller wrote it, for the store to resolve: a ref name (`main`,\n`release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits, or a\nref's earlier value: `<ref>@{N}` the value it held N moves before its current one (`@{0}`\nis the current value), `<ref>@{at=<unix seconds>}` the value set by its newest move\nrecorded at or before that instant (the writer's clock, moves read in journal order);\noptionally followed by `~N` for the N-th first-parent ancestor.",
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "Where the range starts, exclusive: a first-parent ancestor of `changeset`. When omitted,\nits first parent, so the range is `changeset` alone; a root's is the empty tree. At most\n256 changesets (`E_TOO_LARGE`)."
    },
    "target": {
      "default": "main",
      "description": "The ref to land the revert on: `main` or `release/<name>`, which must exist.",
      "type": "string"
    }
  },
  "required": [
    "changeset",
    "reason"
  ],
  "title": "RevertReq",
  "type": "object"
}
```

## Annotations

It adds a changeset onto a ref's head and moves the ref to it, keeping all the ref held. Run again with the same arguments, it finds its change on the ref and adds nothing: `replayed` with an `idempotency_key`, `E_NOTHING_TO_LAND` without.

| Annotation | Value | Meaning |
| --- | --- | --- |
| `destructiveHint` | `false` | True: a tool that changes the store may overwrite or delete what is there, not only add to it. |
| `idempotentHint` | `true` | True: calling it again with the same arguments changes nothing more. |
| `openWorldHint` | `false` | True: it reaches beyond a closed set of things. Every tool here stays inside the store. |
| `readOnlyHint` | `false` | True: the tool does not change its environment, the store. |

Its entry in `tools/list` carries no `_meta`.

## CLI command

[`swarmops revert`](../commands/revert.md) runs the same operation and prints the same envelope.

## Examples

### MCP

```json
{
  "id": 41,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "changeset": "[theirs]",
      "idempotency_key": "revert-1",
      "reason": "the docs were wrong"
    },
    "name": "revert"
  }
}
```

```json
{
  "id": 41,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "text": "{\"api\":1,\"meta\":{\"auth\":\"trust\",\"elapsed_ms\":\"[elapsed_ms]\",\"repo\":\"[tmp]/store\",\"resolved\":{\"[theirs]\":\"[theirs]\",\"main\":\"[id15]\"}},\"ok\":true,\"op\":\"revert\",\"result\":{\"land\":{\"attempts\":1,\"base\":\"[theirs]\",\"files_changed\":1,\"landed\":\"[reverted]\",\"rebased_onto\":\"[id15]\",\"relocated\":false,\"squashed_from\":[\"[id16]\"],\"status\":\"landed\",\"target\":\"main\"},\"reverted\":[\"[theirs]\"]}}",
        "type": "text"
      }
    ],
    "isError": false,
    "structuredContent": {
      "api": 1,
      "meta": {
        "auth": "trust",
        "elapsed_ms": "[elapsed_ms]",
        "repo": "[tmp]/store",
        "resolved": {
          "[theirs]": "[theirs]",
          "main": "[id15]"
        }
      },
      "ok": true,
      "op": "revert",
      "result": {
        "land": {
          "attempts": 1,
          "base": "[theirs]",
          "files_changed": 1,
          "landed": "[reverted]",
          "rebased_onto": "[id15]",
          "relocated": false,
          "squashed_from": [
            "[id16]"
          ],
          "status": "landed",
          "target": "main"
        },
        "reverted": [
          "[theirs]"
        ]
      }
    }
  }
}
```

The server ran as `swarmops mcp --repo [tmp]/store --as agent/worker-17`, after `initialize`. Recorded by the MCP tests. A name in square brackets, such as `[tmp]` or `[c1]`, stands for what differs from run to run: the test's temporary directory, an id, a time, the version.

## Return information

A tool result: the envelope, whose `op` is `revert`, as `structuredContent` and again as the text of its one `content` block. `isError` is true exactly when the envelope's `ok` is false. On success the envelope's `result` is what [`swarmops revert`](../commands/revert.md#return-information) returns.

## Errors

A failed call is a tool result with `isError: true`; the envelope's `error.code` is one of the [error codes](../errors.md).

- [`E_INVALID_ARG`](../errors.md#e_invalid_arg): arguments that do not fit `RevertReq`, a field named twice among them included, or that break one of the server's own rules.
- [`E_IDENTITY_REQUIRED`](../errors.md#e_identity_required): the server runs with no identity.
- [`E_NOT_A_REPO`](../errors.md#e_not_a_repo): the server's store is not there; the server serves on.
- [`E_INTERNAL`](../errors.md#e_internal): the operation panicked, a bug.

Then the errors of the operation, as [`swarmops revert`](../commands/revert.md#errors) lists them.

## See also

- [`swarmops revert`](../commands/revert.md)
- [MCP tools](README.md)
- [MCP integration](../../develop/mcp.md)
- [Error codes](../errors.md)
