OfficeCLI Batch Command: How Atomic Transaction Rollback Works

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 and the resident server's execution logic in 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 (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 (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:

  1. Request ParsingProcessRequest creates a ResidentRequest and detects the batch verb (lines 1154-1156).

  2. Editability PromotionExecuteBatch 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

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

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

batch.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)

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

// 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, supporting STDIN, file input, or inline JSON.
  • Atomic rollback relies on a pre-batch disk snapshot created in 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 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 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 (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 (lines 1154-1338), with handler-specific disposal logic in files like WordHandler.cs and 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 around lines 1330-1338.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →