# How OfficeCLI Batch Commands Handle Atomicity and Error Recovery

> Learn how OfficeCLI batch commands ensure atomicity and error recovery with deferred saves, in-memory rollbacks, and clear JSON error signaling for reliable operations.

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

---

**OfficeCLI implements atomic batch operations by deferring all saves until batch completion and performing full in-memory rollbacks when any item fails, while providing robust error signaling through JSON envelopes and exit codes.**

The OfficeCLI `batch` command is designed for complex document mutations that must either fully succeed or leave the file untouched. According to the iOfficeAI/OfficeCLI source code, this is achieved through a multi-layered coordination between [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) and document-specific handlers.

## Pre-Batch State Capture and DeferSave Barrier

When a batch starts, the resident server immediately establishes two protective mechanisms in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) (lines 1152–1156):

1. **Pre-batch snapshot** — The on-disk file state is preserved as the rollback point
2. **DeferSave activation** — Intermediate autosaves are disabled for the entire batch duration

```csharp
// From ResidentServer.cs around line 1152-1156
_handler.DeferSave = true;  // Blocks intermediate saves
// Pre-batch state recorded for potential rollback

```

This `DeferSave` flag ensures that **only one final `Save()`** occurs after all batch items complete successfully. Without this barrier, partial saves would corrupt the rollback capability.

## Batch Item Execution Under Shared Context

All batch items flow through `CommandBuilder.ApplyBatchItems`, invoked from [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) around line 1304. Key execution properties include:

- Each item runs under the same `DeferSave` context inherited from the batch initialization
- The `skipResidentOnlyCommands` flag filters out state-interfering commands (open/close operations) that would break batch isolation
- The in-memory DOM accumulates changes without persisting to disk

## Atomic Rollback on Failure

If the batch is **atomic** (the default mode) and **any single item fails**, the server executes a complete rollback. The implementation in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) lines 1320–1339 demonstrates this:

```csharp
if (atomic && anyFailed) {
    // Mark handler to discard poisoned DOM without writing
    switch (_handler) {
        case OfficeCli.Handlers.WordHandler w: 
            w.DiscardOnDispose = true; 
            break;
        case OfficeCli.Handlers.ExcelHandler x: 
            x.DiscardOnDispose = true; 
            break;
        case OfficeCli.Handlers.PowerPointHandler p: 
            p.DiscardOnDispose = true; 
            break;
    }
    
    _handler.Dispose();  // Discard corrupted in-memory state
    _handler = DocumentHandlerFactory.Open(_filePath, editable: true);  // Reload original
    
    // Restore deferred-save invariants for Word documents
    if (_handler is OfficeCli.Handlers.WordHandler wh2) {
        wh2.DeferSave = true;
        wh2.AdoptPendingWholeParts(preBatchWholeParts);
    }
    
    _dirty = false;
    rolledBack = true;
}

```

**Critical guarantees provided:**
- The **pre-batch file on disk** serves as the immutable rollback point
- `DiscardOnDispose` prevents any partial writes during disposal
- After rollback, the in-memory DOM exactly matches the original file
- Subsequent commands operate on a consistent, pre-batch state

## Best-Effort Non-Atomic Mode

OfficeCLI supports partial commits via the `--best-effort` flag, parsed in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) around line 1193. When enabled:

- Failures are reported but processing continues
- Successfully processed items remain committed
- The batch does not trigger rollback logic

```bash

# Best-effort: continue after failures, keep successful changes

officecli batch --best-effort --commands \
  "set /slide[1]/title --prop text='Applied'" \
  "set /slide[99]/title --prop text='Fails'" \
  "add /slide[2] --type shape --prop text='Also Applied'"

```

## Error Signaling and Exit Codes

The batch command provides multiple feedback channels for script integration:

| Output Mode | Failure Indicator | Exit Code Behavior |
|-------------|-------------------|------------------|
| JSON | `"success": false` in envelope | `0` = success, `1` = failure, `2` = unsupported-property warnings |
| Text | Error messages to stderr | Calculated matching JSON semantics (lines 894–896) |

The server tracks batch health via `_lastBatchHadFailure` and embeds the final status:

```bash

# JSON mode for programmatic consumption

officecli batch --json --commands \
  "set /slide[1]/title --prop text='Title'"

# Output envelope structure:

# {

#   "success": true,

#   "data": { ... },

#   "warnings": []

# }

```

Standard error streams receive warnings (e.g., unrecognized LaTeX markers) even in success cases, enabling downstream scripts to detect and respond to non-fatal issues.

## Watch Notification Synchronization

For live preview scenarios, batch completion triggers `NotifyWatchFullRefresh()` (lines 792–800 in [`ResidentServer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs)) with one exception: **rolled-back batches do not send refresh notifications**, preventing stale preview states from displaying discarded changes.

## Summary

OfficeCLI batch command error recovery and atomicity rely on five core mechanisms:

- **Pre-batch state preservation** — Original file serves as rollback point
- **DeferSave barrier** — Single final save prevents intermediate persistence
- **Handler-specific disposal** — `DiscardOnDispose` flags in `WordHandler`, `ExcelHandler`, and `PowerPointHandler` eliminate poisoned DOM writes
- **Full in-memory rollback** — Reload from disk restores original state on any failure
- **Explicit exit code contracts** — JSON envelopes and standardized codes enable reliable automation

## Frequently Asked Questions

### What happens to my document if one command in a batch fails?

If running in default atomic mode, the entire batch rolls back. The `DiscardOnDispose` property is set on the handler, the in-memory DOM is discarded, and the original file is reloaded from disk. No partial changes persist.

### How do I allow partial success in a batch?

Add the `--best-effort` flag when invoking the batch command. This disables atomic rollback; failures are reported but already-processed items remain committed. The exit code still indicates failure if any item failed.

### Why does the batch command use DeferSave instead of temporary files?

`DeferSave` keeps all mutations in memory without filesystem I/O, making rollback instantaneous (handler disposal + reload) rather than requiring file restoration operations. This approach also prevents disk space exhaustion from large temporary copies.

### Can I detect batch failures in shell scripts?

Yes. Exit codes follow a strict contract: `0` for complete success, `1` for any failure, `2` for warnings about unsupported properties. In JSON mode, parse the `"success"` field in the response envelope for definitive status.