---
title: "Safe writes"
description: "How the Bucky CLI previews, confirms, records, undoes, and retries changes — including batch writes of up to 25 items."
last_updated: "2026-10-04"
---

Every command that changes something in Bucky — creating a project, attaching parcels,
saving a buy box, exporting a report — follows the same rules. You can preview it,
you confirm it, it leaves a receipt, and where possible you can undo it.

## Preview with `--dry-run`

```bash
bucky projects update <project-id> --name "Main Street Infill" --dry-run
```

A dry run prints the request it would send, a field-by-field preview of the change,
and whether the change can be undone. Nothing is sent to Bucky.

## Confirm

- **In a terminal**, the CLI shows the same preview and asks `[y/N/edit]`. Choose
  **edit** to change the input in your editor before sending.
- **Everywhere else** (`--json`, piped output, CI), the CLI won't write unless you
  pass `--yes`. Review the `--dry-run` output first, then run the same command with
  `--yes`. Without it you get `MUTATION_CONFIRMATION_REQUIRED`.

```bash
bucky --json notes add <project-id> --body "Need a geotech report" --dry-run
bucky --json notes add <project-id> --body "Need a geotech report" --yes
```

## Receipts

Before the first change reaches Bucky, the CLI writes a receipt on your machine. When
the change finishes, it updates the same receipt to `completed`, `failed`, `cancelled`,
or `unknown`. Receipts hold no secrets.

```bash
bucky operations list
bucky operations show <receipt-id>
```

Receipts are kept per profile under `${XDG_STATE_HOME:-~/.local/state}/bucky/`.

## Undo

```bash
bucky undo <receipt-id>
bucky undo --last --yes
```

Undo applies the inverse recorded on the receipt, for example restoring the previous
project name or deleting a buy box you just created. Not every write can be undone:
`--dry-run` tells you, and so does `bucky --json agent-context` (`mutation.reversible`).
Sending a chat message, exporting a report, and running an opportunity screen cannot be
undone from the CLI.

## Recovering from an unknown outcome

If the network drops mid-write, the receipt ends as `unknown`: the change may or may not
have happened.

- **`projects create`, `projects duplicate`, `programs create`** — re-run with
  `--retry-operation <receipt-id> --yes` and the same flags. The CLI reuses the original
  idempotency key, so Bucky won't create a second copy.
- **Everything else** — check before you retry. Each command's recovery hint says how,
  for example `bucky screen list` before re-running `screen create`, or `bucky projects
list` before re-running `projects create-assembly`.

Sending the same payload twice does not de-duplicate on its own.

## Batch writes

Thirty write commands — including `projects create`, `projects update`, `notes add`,
`references add`, `financing add`, and `parcels set-role` — accept a JSON **array** on
`--input`. One run shares one sign-in and connection, and writes one receipt per item
plus a summary.

```bash
bucky --json notes add --input ./notes.json --dry-run
bucky --json notes add --input ./notes.json --yes
bucky --json notes add --input ./notes.json --continue-on-error --yes
```

To see which commands batch and which fields each item may set:

```bash
bucky --json agent-context | jq '.data.commands | with_entries(select(.value.batch)) | keys'
```

**Limits.** Up to 25 items per run, checked before anything is sent.

**One run can span projects.** Pass `<project-id>` to apply it to every item, or leave
it out and give each item its own `projectId`. Giving both, with different values, is an
error.

```json
[
  { "projectId": "…", "body": "Need a geotech report" },
  { "projectId": "…", "body": "Call the surveyor", "title": "Follow-up" }
]
```

**Failures.** Every item is validated first, so a bad row stops the run before anything
is written. Items are then written in order, stopping at the first failure unless you
pass `--continue-on-error`. Any failure exits `1` with `BATCH_PARTIAL_FAILURE` and a
per-item breakdown:

```json
{
  "batch": true,
  "total": 3,
  "succeeded": 1,
  "failed": 1,
  "skipped": 1,
  "items": [
    { "index": 0, "status": "succeeded", "receiptId": "…", "resourceId": "…" },
    { "index": 1, "status": "failed", "receiptId": "…", "error": { "code": "HTTP_400" } },
    { "index": 2, "status": "skipped", "reason": "stopped-after-failure" }
  ]
}
```

**Safe re-runs of project imports.** In a batch `projects create`, each item gets its
own idempotency key. Set `idempotencyKey` on each item yourself (for example your own
record id) so re-running the same import file never creates duplicates:

```json
[
  {
    "name": "North East False Creek",
    "lat": 49.27,
    "lng": -123.11,
    "idempotencyKey": "crm-3715f18b"
  },
  { "name": "King Street", "idempotencyKey": "crm-0777802a" }
]
```

Commands where a resend would duplicate work don't batch: `projects create-assembly`,
`projects invites create`, `designs set-primary`, `chat turn`, and `documents upload`.


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
