<!-- Generated by crates/swarmops-cli/src/reference.rs from the code: do not edit. Change the code, then run SWARMOPS_REGEN_REFERENCE=1 with the regen_reference test. -->

# swarmops land

Merge a change three-way onto a ref and publish it there as one new changeset.

The change is HEAD with its first-parent chain since the base, squashed: the new changeset's parent is the ref's head, it holds what the chain changed merged with what the ref gained since the base, and the ref moves to it in the same transaction. A ref that moves meanwhile makes the land merge again onto its new head and retry, up to --max-attempts times. A conflict is E\_CONFLICT (exit 3), with each conflicting file in details.report, and nothing is recorded; land again with --resolve to resolve it without a checkout.

```yaml
title: "swarmops land"
description: "Merge a change three-way onto a ref and publish it there as one new changeset."
group: "write"
since: "0.1.0"
mutates: true
identity_required: true
mcp_tool: "land"
envelope_op: "land"
exit_codes: [0, 1, 3]
errors: [E_CONFLICT, E_NOTHING_TO_LAND]
syntax: "swarmops land [OPTIONS] <HEAD>"
```

## Syntax

```text
swarmops land [OPTIONS] <HEAD>
```

## Required arguments

### `HEAD`

Syntax: `<HEAD>`.

The changeset to land: the change's head, such as the changeset a commit made.

## Optional arguments

The command also takes the [global options](../cli.md#global-options).

### `--base`

Syntax: `--base <REV>`.

Where the change starts: a first-parent ancestor of HEAD, whose own changes are not landed (default: the merge base of HEAD and the ref's head, along first parents).

### `--target`

Syntax: `--target <REF>`.
Default: `main`.

The ref to land on: main or release/&lt;name&gt;.

### `--message`

Syntax: `-m, --message <MSG>`.

The new changeset's message (default: HEAD's).

### `--key`

Syntax: `--key <KEY>`.

Makes a retry of this land safe: 1 to 64 letters, digits and '-', such as a uuid v7. Make a new one for each change you land, and give it again only to retry that land.

A land with a key first looks down the ref's first parents, to the change's base, for a changeset landed with the same key: one squashed from this HEAD is the land made before, which the land replays (status: replayed) without writing anything; one from another head is E\_INVALID\_ARG. Without a key, the same land run again finds its change on the ref already: E\_NOTHING\_TO\_LAND.

### `--max-attempts`

Syntax: `--max-attempts <N>`.
Default: `8`.

How many times to try to publish before E\_CAS\_RETRY\_EXHAUSTED: 1 to 32.

### `--max-conflict-bytes`

Syntax: `--max-conflict-bytes <N>`.
Default: `32768`.

The most bytes of hunk text an E\_CONFLICT report carries; past them it leaves the texts out (truncated) and keeps every line position and blob id.

### `--resolve`

Syntax: `--resolve <FILE>`.

Resolve the conflicts an E\_CONFLICT report named, without a checkout: resolutions as a JSON object in FILE, or - for stdin, at most 64 MiB (swarmops schema ResolutionsInput).

{"against": THEIRS, "files": {PATH: RESOLUTION, ...}}: THEIRS is the report's theirs, by its full id, and each RESOLUTION one of {"take": "ours"}, {"take": "theirs"}, {"content": CONTENT} with an optional "mode" (CONTENT as a commit's change gives it: inline, base64, a file, or a stored blob by id) and {"delete": true}. A resolution decides its path and every path below it; the rest of the merge stands.

A resolution applies while the ref holds its path as THEIRS did, whether the path conflicts or not. A path the ref changed since is stale: the land fails with E\_CONFLICT again, its report naming the path in stale, as it does for each conflict left unresolved. Read the report's theirs, and resolve against it.

## Examples

### CLI

```sh
swarmops --repo [tmp]/store --as agent/worker-17 land [head] --key land-7
```

```json
{
  "ok": true,
  "op": "land",
  "api": 1,
  "result": {
    "status": "landed",
    "landed": "[landed]",
    "rebased_onto": "[theirs]",
    "target": "main",
    "base": "[t0]",
    "attempts": 1,
    "relocated": false,
    "files_changed": 1,
    "squashed_from": [
      "[head]"
    ]
  },
  "meta": {
    "repo": "[tmp]/store",
    "elapsed_ms": "[elapsed_ms]",
    "resolved": {
      "[head]": "[head]",
      "main": "[theirs]"
    },
    "auth": "trust"
  }
}
```

Exit code 0. Recorded by the CLI 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.

### MCP

No recorded example yet.

## Details

`land` is how agents publish to a shared ref. It takes a change, merges it three-way onto
whatever the target ref holds now, and publishes the result there as one new changeset, the
ref's move with it, in one transaction. Nothing is recorded unless the whole land succeeds.
[Commit and land](../../guides/commit-and-land.md) is the guide; this section is the
command's behaviour in full.

### What a land publishes

The change is HEAD with its first-parent chain after the base:

```text
        base ── c1 ── c2 ── HEAD         the change: c1, c2 and HEAD
          │
  main: base ── t1 ── t2                 what main gained since the base
                       │
                       └── landed        one new changeset, parent t2
```

- `--base` defaults to the merge base of HEAD and the target's head, along first parents. Given,
  it must be a first-parent ancestor of HEAD; it need not be on the target.
- The chain is squashed: `landed` holds what the chain changed merged with what the target
  gained since the base, its parent is the target's head (`rebased_onto`), and the target stays
  linear. `squashed_from` lists the chain, oldest first.
- The changeset's author is HEAD's author and its committer is you. Its message is HEAD's unless
  `-m` gives one. Its metadata keeps HEAD's `agent` and `run_id`, and adds `lander` (your agent),
  `lander_run_id` (your run id, when you have one), `land_key` (with `--key`) and
  `squashed_from`. [`show`](../commands/show.md) prints them.
- The target's move is recorded for your agent; [`op log`](../commands/op-log.md) lists the
  land as one operation, and [`op undo`](../commands/op-undo.md) can take it back.

### The target

`--target` is `main` (the default) or `release/<name>`, and it must exist: a name of another
form is `E_REF_NAME_INVALID` (exit 1), and a ref that does not exist is `E_REV_NOT_FOUND`
(exit 6). [Land on a release ref](../../guides/commit-and-land.md#land-on-a-release-ref)
shows a release ref.

### When the target moves meanwhile

A land reads the target, merges, and publishes by compare-and-swap. When another writer moved
the target in between, the land merges again onto the new head and tries again, up to
`--max-attempts` times (8 by default, 1 to 32). `attempts` says how many it took. `relocated`
says the publish found the target moved and put the change on the new head itself, without
another attempt. When every attempt loses, the land fails with `E_CAS_RETRY_EXHAUSTED` (exit 4,
retryable), `details.attempts` and `details.actual` set, and nothing recorded: run the same land
again. [Two agents landing at once](../../guides/commit-and-land.md#two-agents-landing-at-once)
shows the race.

### Conflicts

A change that does not merge is `E_CONFLICT` (exit 3), and nothing is recorded. The target is
as the other writers left it. `details.report` names:

- `base`, `ours` (your change's head) and `theirs` (the target's head it was merged with), by
  full id;
- each conflicting file: its `path`, its `kind`, the blob ids of the sides that hold it, its hunks (each
  side's lines, numbered from 1, both ends included), and the target's changeset and agent that
  last changed it (`theirs_changeset`, `theirs_agent`);
- `kinds`: `content` (the same or adjacent lines of a text file), `binary`, `delete_modify`,
  `modify_delete`, `add_add`, `mode` or `type_change`;
- `auto_merged`, the paths both sides changed that merged cleanly.

Hunks carry their texts until they pass `--max-conflict-bytes` (32,768 by default); past it the
report sets `truncated` and leaves every text out, keeping the line positions and the blob ids,
from which [`cat`](../commands/cat.md) reads any side. With `--max-conflict-bytes 0` a report
carries no hunk text. The error's hint is the `cat` of the first conflicting path at `theirs`, then
the land again with `--resolve`.

### Resolving without a checkout

`--resolve FILE` (or `-` for stdin) lands again with a decision for each conflicting path:

```json
{"against": "<the report's theirs, full id>",
 "files": {"src/lib.rs": {"take": "theirs"},
           "src/new.rs": {"content": {"inline": "pub fn hi() {}\n"}},
           "docs/old.md": {"delete": true}}}
```

Each path takes exactly one of `take` (`ours` or `theirs`), `content` (with an optional `mode`,
the content in any form a commit's change takes) or `delete`, and decides that path and every
path below it. A resolution applies while the target holds its path as `against` did. When the
target changed the path again in the meantime, the land fails with a fresh `E_CONFLICT` whose
report names the path in `stale`: read the new `theirs`, and resolve against it. A conflict left
without a resolution fails the land the same way. [Resolve a
conflict](../../guides/commit-and-land.md#resolve-a-conflict) walks through one.

### `--key`: a retry that is safe

A land key is 1 to 64 letters, digits and `-`, such as a uuid v7; anything else is
`E_INVALID_ARG`, before anything is read. With a key, the land first looks down the target's
first parents, to the change's base, for a changeset landed with the same key:

| What it finds | Result |
| --- | --- |
| A changeset squashed from this HEAD | `status: replayed`, the first land's result, nothing written. |
| A changeset squashed from another head | `E_INVALID_ARG` (exit 2), `details.key` and `details.landed`: the key was used for another change. |
| None | The land runs. |

Without a key, a land run again after it succeeded fails with `E_NOTHING_TO_LAND` (exit 1),
`details.reason` `no_changes`. A HEAD that is on the target already fails with `reason`
`on_target`. Make a new key for each change you land, and give it again only to retry that land
([Land keys](../../guides/commit-and-land.md#land-keys)).

### Patterns

- Many agents, one trunk: each agent commits its change on the head it read, with `--parent`,
  and lands it with a key. Landings are serialised by the target's compare-and-swap; a
  non-conflicting race costs a re-merge, not a failure.
- On `E_CONFLICT`, an agent reads the report, decides each path, and lands again with
  `--resolve`; nothing needs to be undone, since nothing was recorded.
- On exit 4, run the same command again with the same key.
- Through MCP the tool is `land`, taking the same fields as `LandReq` (`swarmops schema
  LandReq`); there `resolutions.against` must be a full 64-hex id ([Landing through
  MCP](../../develop/mcp.md#landing-through-mcp)).

### Other failures

- A base that is not a first-parent ancestor of HEAD, a HEAD and target that share no
  first-parent ancestor (give `--base`), and a chain that holds a merge changeset (more than one
  parent) are each `E_INVALID_ARG` (exit 2).
- `--max-attempts` outside 1 to 32 is `E_INVALID_ARG`, before anything is read.
- No identity is `E_IDENTITY_REQUIRED` (exit 5).
- `E_BUSY` (exit 4): another writer held the store's lock for the whole wait; nothing was
  recorded.

### Performance

A lost race re-merges only the paths the change touched onto what the target gained, rather
than the whole change from its base. Its cost is not measured yet ([a land's incremental
re-merge](../../perf.md#m4--a-lands-incremental-re-merge-not-measured-yet)).

## Return information

`result` is a `LandResp` (`swarmops schema LandResp` prints its JSON Schema).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attempts` | `integer` | yes | How many times the land tried to publish. |
| `auto_merged` | `array of string` | no | The paths both sides changed that merged cleanly. Omitted when empty. |
| `base` | `string` | yes | The changeset the change was taken from: what `head`'s first-parent chain added after it is what landed. |
| `files_changed` | `integer` | yes | How many paths the landed changeset changes. |
| `landed` | `string` | yes | The landed changeset: the change squashed into one changeset on the target. |
| `rebased_onto` | `string` | yes | The target's head the change landed on: `landed`'s parent. |
| `relocated` | `boolean` | yes | The target moved after the land read it, and the publish put the change on the new head itself, without another attempt. |
| `squashed_from` | `array of string` | yes | The changesets the land squashed, oldest first: `head`'s first-parent chain after `base`, ending at `head`. |
| `status` | `LandStatus` | yes | `landed`, or `replayed` when a land with the same `idempotency_key` had landed the change already. |
| `target` | `string` | yes | The ref it landed on. |
| `warnings` | `array of string` | no | Problems found after the land became durable, such as failed housekeeping; the land itself succeeded. Omitted when empty. |

Envelope `op`: `land`.

Exit codes:

- `0`: Success.
- `1`: The operation failed.
- `3`: The change conflicts with what it lands on: resolve it, then run the operation again.

## Errors

- [`E_CONFLICT`](../errors.md#e_conflict) (exit 3): The change conflicts with the target: `details.report` lists each conflicting file with its kind, its hunks and who changed it on the target.
- [`E_NOTHING_TO_LAND`](../errors.md#e_nothing_to_land) (exit 1): The land has nothing to add to the target: its head is on the target already, or its change is (`details.reason` says which).

Any command that opens a store may also fail with a store error, such as [`E_NOT_A_REPO`](../errors.md#e_not_a_repo), [`E_IO`](../errors.md#e_io) or [`E_STORE_CORRUPT`](../errors.md#e_store_corrupt); a command line the grammar refuses is [`E_INVALID_ARG`](../errors.md#e_invalid_arg) (exit 2).

## See also

- [Operating SwarmOps: Agents commit and land](../../operations.md#2-agents-commit-and-land)
- [`swarmops commit`](commit.md)
- [`swarmops blob put`](blob-put.md)
- [`swarmops blob has`](blob-has.md)
- [CLI reference](../cli.md)
- [Error codes](../errors.md)
