> ## Documentation Index
> Fetch the complete documentation index at: https://docs.repost.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Safe mutations

> How agents change state safely: scope to one target, preview the blast radius, make it idempotent, gate bulk behind --yes, and wait on the result.

A write in Repost is **not** a simulation. Replaying an event re-delivers it to the live target; pausing a forwarder stops real traffic; provisioning creates real infrastructure. Every mutating command an agent runs follows the same shape — learn it once here, then each playbook shows its command-specific parts.

## What mutates

| Command                                  | Effect                                   | Scope   | Guard                                              |
| ---------------------------------------- | ---------------------------------------- | ------- | -------------------------------------------------- |
| `replay`                                 | Re-delivers events to a live target      | `write` | `--dry-run`, `--yes` for bulk, `--idempotency-key` |
| `forwarder pause` / `resume` / `disable` | Starts, stops, or retires delivery       | `write` | Explicit `--bucket` + `--forwarder` (no picker)    |
| `bucket create` / `forwarder create`     | Provisions a new route                   | `write` | Positional name; re-check before re-creating       |
| `init`                                   | Provisions a bucket + forwarder together | `write` | `--idempotency-key` (idempotent by design)         |

Read-only commands (`events`, `forwards`, `health`, `whoami`, `capabilities`, `docs`) never need these guards — this page applies only to the writes above.

## The pattern

<Steps>
  <Step title="Confirm the write scope">
    Mutations need a `write` token. A missing scope fails cleanly with `forbidden_scope` (exit 3) before anything changes — check `repost auth status --json` up front. See [Authentication](/agents/authentication#scope-model).
  </Step>

  <Step title="Resolve stable IDs first">
    Act on concrete IDs or a bounded selector you got from a search — never a vague or open-ended one. Arrive at a mutation from [Diagnose webhooks](/agents/diagnose-webhooks), not a guess.
  </Step>

  <Step title="Preview the blast radius">
    Where a command supports it (`replay --dry-run`), read the predicted count and gate on it before executing. If it is larger than expected, tighten the selector instead of proceeding.
  </Step>

  <Step title="Make it idempotent">
    Pass `--idempotency-key` so a retried command re-attaches to the existing work instead of doing it twice.
  </Step>

  <Step title="Gate bulk behind --yes">
    Agents run non-interactively, so bulk mutations refuse to proceed without `--yes`. Pass it deliberately, not reflexively.
  </Step>

  <Step title="Wait, then branch on the exit code">
    Don't assume success. Wait for terminal state (`replay wait`, or re-verify with `health`) and branch on the exit code — never the status string.
  </Step>
</Steps>

## Run non-interactively

A human running `forwarder pause` with no arguments gets an interactive picker. An agent must never reach it — it blocks forever. **Always pass the selectors** each mutating command needs:

| Command                                  | Required selectors                                             |
| ---------------------------------------- | -------------------------------------------------------------- |
| `replay`                                 | `--forwarder` (and `--bucket` when the forwarder is named)     |
| `forwarder pause` / `resume` / `disable` | `--bucket` (`-b`) and `--forwarder` (`-f`)                     |
| `bucket create` / `forwarder create`     | The positional name, plus `--target` for an external forwarder |

<Note>
  `--bucket` is the canonical selector for agents. `--bucket-slug` remains accepted as a compatibility alias, but new automation should use `--bucket` or `-b`.
</Note>

## Idempotency

`--idempotency-key` makes a write safe to retry. If a command times out or the connection drops, re-run the **identical** command with the same key: the API re-attaches to the original work instead of starting a second one.

```json theme={null}
{ "schema": "repost.replay.create/v1", "data": { "job_id": "rpl_01JZ9F2K7M", "status": "IN_PROGRESS", "deduplicated": true } }
```

`deduplicated: true` means the work is already running — treat it as success. `init` is idempotent the same way: re-running with the same key (or the same name and target) returns the existing bucket and forwarder rather than creating duplicates.

## Recover from conflict

A `conflict` error (exit `1`, HTTP 409/412) means the state changed under you between read and write — a forwarder was paused by someone else, or a job already moved on. Don't retry blindly: re-read the current state, reconcile, then retry the mutation. The full error contract is in [Output & errors](/agents/output-and-errors#handle-errors).

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Replay deliveries" icon="rotate-ccw" href="/agents/replay-deliveries" cta="Re-deliver safely" arrow="true">
    The full replay flow: dry-run, selectors, idempotency, and exit-code waiting.
  </Card>

  <Card title="Guard a deployment" icon="shield-check" href="/agents/guard-deployments" cta="Pause & resume" arrow="true">
    Pause forwarders around a deploy and gate on health.
  </Card>

  <Card title="Provision a target" icon="circle-plus" href="/agents/provision-targets" cta="Set up a route" arrow="true">
    Create a bucket and forwarder idempotently.
  </Card>
</Columns>
