How OfficeCLI Batch Commands Handle Atomicity and Error Recovery
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 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 (lines 1152–1156):
- Pre-batch snapshot — The on-disk file state is preserved as the rollback point
- DeferSave activation — Intermediate autosaves are disabled for the entire batch duration
// 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 around line 1304. Key execution properties include:
- Each item runs under the same
DeferSavecontext inherited from the batch initialization - The
skipResidentOnlyCommandsflag 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 lines 1320–1339 demonstrates this:
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
DiscardOnDisposeprevents 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 around line 1193. When enabled:
- Failures are reported but processing continues
- Successfully processed items remain committed
- The batch does not trigger rollback logic
# 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:
# 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) 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 —
DiscardOnDisposeflags inWordHandler,ExcelHandler, andPowerPointHandlereliminate 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →