# OfficeCLI Batch Command: How Atomic Transaction Rollback Works

> Learn how the OfficeCLI batch command uses atomic transaction rollback to process JSON commands. Discover rollback mechanisms like deferred saves and snapshots for seamless execution.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: internals
- Published: 2026-07-25

---

**The OfficeCLI batch command processes JSON arrays of commands atomically by deferring saves, creating a pre-batch disk snapshot, and discarding the in-memory DOM to rollback to the saved state if any item fails.**

The **OfficeCLI batch command** in the `iOfficeAI/OfficeCLI` repository enables bulk document modifications through a single request while guaranteeing data integrity. This feature allows users to submit multiple add, set, or remove operations as a JSON array, with the resident server ensuring that either all changes apply successfully or the document rolls back to its original state. Understanding the atomic transaction rollback mechanism requires examining the interplay between the command parser in [`CommandBuilder.Batch.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.Batch.cs) and the resident server's execution logic in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs).

## What Is the OfficeCLI Batch Command?

The `officecli batch` verb accepts a JSON array containing individual OfficeCLI commands and executes them sequentially against a target document. Each array element is a JSON object specifying a command type—such as `add`, `set`, `remove`, or `get`—along with arguments like `path`, `props`, or `parent`.

You can supply the batch via three methods:
- **`--commands`** followed by a JSON string
- **`--input <file>`** pointing to a JSON file
- **`--input -`** to read from STDIN

The command definition and help text reside in **[`CommandBuilder.Batch.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.Batch.cs)** (lines 12-31), while the option parsing logic occupies lines 18-48 of the same file. When running against a resident server, the batch processes entirely in-memory, flushing to disk only once at completion or during autosave cycles.

## Atomic Transaction Rollback Implementation

### The Pre-Batch Snapshot

When executing an **atomic batch** (the default behavior), the resident server first establishes a rollback point by writing the current in-memory DOM to disk. In **[`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs)** (lines 1242-1249), the `ExecuteBatch` method checks if the document is dirty and the flush policy permits, then calls `handler.Save()` to create the "pre-batch" state on disk before clearing the `_dirty` flag. This flush barrier ensures the on-disk file represents a clean restoration point.

### Deferred Saves and Execution Flow

During batch processing, the server sets `DeferSave = true` on the handler (lines 1283-1285) to prevent per-item serialization. This optimization eliminates O(N²) write operations and ensures the disk file remains unchanged throughout the batch execution. The shared loop `CommandBuilder.ApplyBatchItems` invoked at lines 1301-1303 replays each command against the in-memory document without intermediate disk writes.

### Failure Detection and DOM Discard

After processing all items, the server evaluates `results.Any(r => !r.Success)` at lines 1315-1317 to detect failures. If any item failed in atomic mode, the system triggers rollback by setting `DiscardOnDispose = true` on the concrete handler (whether `WordHandler`, `ExcelHandler`, or `PowerPointHandler`). The handler then disposes, discarding the poisoned in-memory DOM.

### Rollback Mechanism Details

The rollback completes when `DocumentHandlerFactory.Open` reloads a fresh handler from the pre-batch file (lines 1319-1327). Because saves were deferred and the pre-batch snapshot was flushed before execution began, the on-disk file never changed during the failed batch. The newly opened handler inherits the resident's editability settings, restoring the exact logical state that existed before the batch started. This guarantees **all-or-nothing** semantics: either every command persists or none do.

## Execution Flow in ResidentServer.cs

The batch execution follows a strict sequence defined in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs):

1. **Request Parsing** – `ProcessRequest` creates a `ResidentRequest` and detects the `batch` verb (lines 1154-1156).

2. **Editability Promotion** – `ExecuteBatch` calls `PromoteToEditable` once to make the document writable before processing begins.

3. **Atomic Barrier** – If atomic mode is active and the file is dirty, the server performs the pre-batch flush (lines 1242-1249).

4. **Batch Replay** – The server invokes `CommandBuilder.ApplyBatchItems` through `ExecuteBatch` to run items sequentially (lines 1301-1303).

5. **Result Collection** – Each item's success status, output, and LaTeX warnings populate a `BatchResult` list.

6. **Conditional Rollback** – Upon failure in atomic mode, the server discards the handler and reopens from the pre-batch file (lines 1310-1325).

7. **Exit Code Propagation** – `_lastBatchHadFailure` sets the exit code to non-zero (lines 1173, 1158-1162), with JSON mode returning `"success":false` (lines 1084-1089).

## Atomic vs. Best-Effort Modes

**Atomic Mode (Default):** When `--best-effort` is omitted and at least one mutating item exists, the batch operates as a transaction. Any failure triggers a complete rollback to the pre-batch state. The decision logic resides in `ExecuteBatch` around lines 1330-1338.

**Best-Effort Mode:** Passing `--best-effort` applies successful items while skipping failures without rollback. This legacy "apply-what-succeeds" behavior modifies the document partially and does not guarantee atomicity.

## Code Examples

### Simple Batch from the CLI

```bash
officecli batch mydoc.pptx \
    --commands '[{"command":"add","parent":"/slide[1]","type":"shape","props":{"text":"Hello"}},{"command":"set","path":"/slide[1]/shape[1]","props":{"bold":"true"}}]' \
    --json

```

This example submits two items—an `add` and a `set`—and requests JSON output. The envelope returns `"success":true` only if both operations succeed.

### Atomic Batch with Explicit Rollback

```bash
officecli batch report.docx \
    --input batch.json \
    --stop-on-error

```

**[`batch.json`](https://github.com/iOfficeAI/OfficeCLI/blob/main/batch.json):**

```json
[
  {"command":"set","path":"/body/p[1]","props":{"color":"red"}},
  {"command":"add","parent":"/body","type":"table","props":{"rows":"3","cols":"4"}}
]

```

If the first `set` fails (for example, due to an unsupported property), the entire batch aborts and `report.docx` reverts to its pre-command state exactly.

### Best-Effort Batch (Partial Apply)

```bash
officecli batch data.xlsx \
    --commands '[{"command":"set","path":"/sheet[1]/cell[1]","props":{"value":"100"}},{"command":"set","path":"/sheet[1]/cell[2]","props":{"invalid":"true"}}]' \
    --best-effort

```

With `--best-effort`, the first cell update persists even if the second fails. No atomic rollback occurs.

### Programmatic Usage via Resident Server

```csharp
// Invoking a batch programmatically against a resident server
var request = new ResidentRequest
{
    Command = "batch",
    Json = true,
    Args = new Dictionary<string, string>
    {
        { "batchJson", "[{\"command\":\"add\",\"parent\":\"/slide[1]\",\"type\":\"shape\",\"props\":{\"text\":\"Hi\"}}]" },
        { "stopOnError", "true" }
    }
};

var response = residentServer.ProcessRequest(JsonSerializer.Serialize(request));
Console.WriteLine(response);

```

This C# example mirrors the internal logic in `ResidentServer.ProcessRequest`, passing batch JSON via the `batchJson` argument with atomic rollback enabled through `stopOnError`.

## Summary

- The **OfficeCLI batch command** processes JSON command arrays via [`CommandBuilder.Batch.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.Batch.cs), supporting STDIN, file input, or inline JSON.
- **Atomic rollback** relies on a pre-batch disk snapshot created in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) (lines 1242-1249) before deferred execution begins.
- If any item fails in atomic mode, the system sets `DiscardOnDispose = true` and reloads the handler from the pre-batch file, ensuring zero partial writes.
- **`--best-effort`** disables atomicity, allowing partial application of successful commands while reporting failures.
- Exit codes and JSON responses reflect batch success status through `_lastBatchHadFailure` and envelope fields.

## Frequently Asked Questions

### What happens if one command in an OfficeCLI batch fails?

In **atomic mode** (the default), any single failure triggers a complete rollback. The resident server discards the in-memory DOM and reloads the document from the pre-batch snapshot saved to disk at [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) lines 1319-1327. In **best-effort mode**, the failure is recorded but successful commands remain applied.

### How do I enable atomic transaction rollback in OfficeCLI?

Atomic rollback is enabled by default when running `officecli batch` without the `--best-effort` flag. To ensure strict atomicity, include `--stop-on-error` (or ensure `stopOnError` is true in programmatic requests). The server automatically creates the rollback snapshot at lines 1242-1249 of [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) when the document is dirty.

### Where is the batch command implemented in the OfficeCLI source code?

The batch command definition lives in **[`src/officecli/CommandBuilder.Batch.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/CommandBuilder.Batch.cs)** (lines 12-48), which defines the verb, help text, and the shared `ApplyBatchItems` replay loop. The execution logic, including atomic barriers and rollback, resides in **[`src/officecli/ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/ResidentServer.cs)** (lines 1154-1338), with handler-specific disposal logic in files like [`WordHandler.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/WordHandler.cs) and [`ExcelHandler.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ExcelHandler.cs).

### What is the difference between atomic and best-effort batch modes?

**Atomic mode** treats the entire batch as a transaction: all commands succeed or none persist, with automatic rollback to the pre-batch state. **Best-effort mode** (`--best-effort`) applies each command independently, preserving successful changes while logging failures. The mode selection logic appears in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) around lines 1330-1338.