# How to Handle Tracked Changes in DOCX Files Programmatically: A Complete Guide

> Programmatically handle tracked changes in DOCX using LibreOffice headless mode and a Basic macro. Accept all changes efficiently with this comprehensive guide and implementation from anthropics/skills.

- Repository: [Anthropic/skills](https://github.com/anthropics/skills)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You can handle tracked changes in DOCX files programmatically by using LibreOffice in headless mode with a custom Basic macro that invokes the `.uno:AcceptAllTrackedChanges` command, as implemented in the `anthropics/skills` repository.**

The `anthropics/skills` repository provides a production-ready solution for programmatically handling tracked changes in Microsoft Word documents. Whether you need to accept all revisions during an automated document pipeline or extract change metadata for review, the repository's DOCX skill leverages LibreOffice's UNO API to manipulate revision tracking without requiring a GUI.

## Why LibreOffice Is the Engine for Programmatic DOCX Change Acceptance

LibreOffice remains the only open-source office suite capable of executing the "Accept All Tracked Changes" command via programmable interface. The `anthropics/skills` implementation exploits this capability by driving `soffice` in headless mode, ensuring your automation runs on servers and containers without display infrastructure.

The approach guarantees complete compatibility with Microsoft Word's revision tracking format while maintaining process isolation through temporary user profiles.

## The Architecture: How the Accept Changes Script Works

The core logic resides in [`skills/docx/scripts/accept_changes.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/accept_changes.py), which orchestrates three distinct phases: macro deployment, environment preparation, and headless execution.

### Macro Creation and the UNO Dispatcher

The script generates a LibreOffice Basic macro named `AcceptAllTrackedChanges` and writes it to the user profile directory. This macro invokes the UNO dispatcher command `.uno:AcceptAllTrackedChanges`, persists the document, and terminates the application.

The macro is accessed via the URL scheme:

```

vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application

```

### Environment Isolation with soffice.py

The helper module [`skills/xlsx/scripts/office/soffice.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/soffice.py) provides `get_soffice_env()`, which prepares a sanitized execution environment. It detects sandboxed contexts where UNIX sockets are blocked and preloads `lo_socket_shim.so` to circumvent restrictions. The function also sets `SAL_USE_VCLPLUGIN=svp` to ensure the headless SVP (SVP VCL Plugin) backend is active.

### Execution Flow

When you invoke the accept changes function, the script:

1. Validates the input file exists and carries a `.docx` suffix
2. Copies the input to the output location to prevent mutation of the original
3. Ensures the `AcceptAllTrackedChanges` macro is installed in the temporary LibreOffice profile
4. Executes `soffice --headless` with the macro URL and target document
5. Returns a status message indicating success or detailing any LibreOffice errors

## Implementation: Code Examples

### Command-Line Usage

The simplest way to handle tracked changes programmatically is via the module's CLI interface:

```bash
python -m skills.docx.scripts.accept_changes input.docx output.docx

```

This command accepts all tracked changes in `input.docx` and writes the clean document to `output.docx`. LibreOffice must be installed and `soffice` available on your system PATH.

### Python API Integration

For integration into larger applications, import the `accept_changes` function directly:

```python
from pathlib import Path
from skills.docx.scripts.accept_changes import accept_changes

def finalize_document(draft_path: str, final_path: str) -> None:
    """
    Accept all tracked changes in a DOCX file programmatically.
    """
    success, message = accept_changes(draft_path, final_path)
    
    if not success:
        raise RuntimeError(f"Failed to process document: {message}")
    
    print(f"Successfully finalized document: {message}")

# Example usage

finalize_document("contract_draft.docx", "contract_final.docx")

```

### Advanced Manual Invocation

If you require fine-grained control over the LibreOffice execution environment, use the [`soffice.py`](https://github.com/anthropics/skills/blob/main/soffice.py) helpers to construct custom subprocess calls:

```python
import subprocess
from pathlib import Path
from skills.xlsx.scripts.office.soffice import get_soffice_env

def run_custom_accept_macro(doc_path: Path) -> None:
    """
    Manually invoke LibreOffice with the AcceptAllTrackedChanges macro.
    """
    macro_url = (
        "vnd.sun.star.script:Standard.Module1."
        "AcceptAllTrackedChanges?language=Basic&location=application"
    )
    
    cmd = [
        "soffice",
        "--headless",
        "-env:UserInstallation=file:///tmp/libreoffice_docx_profile",
        "--norestore",
        macro_url,
        str(doc_path.resolve())
    ]
    
    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        env=get_soffice_env(),
        check=False
    )
    
    if result.returncode != 0:
        raise RuntimeError(f"LibreOffice execution failed: {result.stderr}")

# Usage

run_custom_accept_macro(Path("document.docx"))

```

### Read-Only Change Extraction with Pandoc

When you only need to view tracked changes without modifying the document, use Pandoc's `--track-changes` flag:

```bash
pandoc --track-changes=all draft.docx -o review_document.md

```

This converts the DOCX to Markdown while preserving insertion and deletion markup, allowing programmatic review of revisions before acceptance.

## Error Handling and Edge Cases

The [`accept_changes.py`](https://github.com/anthropics/skills/blob/main/accept_changes.py) script implements robust validation to prevent common failures. It verifies that input files exist and possess the `.docx` extension before processing. When LibreOffice encounters corrupted documents or permission errors, the script captures stderr and returns a descriptive error message rather than crashing the parent process.

For sandboxed environments—such as containers with disabled UNIX sockets—the [`soffice.py`](https://github.com/anthropics/skills/blob/main/soffice.py) helper automatically preloads `lo_socket_shim.so` and configures the SVP VCL plugin, ensuring the headless automation succeeds even in restricted runtime environments.

## Summary

- **LibreOffice headless mode** is the only reliable open-source method to programmatically accept tracked changes in DOCX files.
- The **[`accept_changes.py`](https://github.com/anthropics/skills/blob/main/accept_changes.py)** script in `anthropics/skills` automates this by deploying a Basic macro that invokes `.uno:AcceptAllTrackedChanges`.
- **Environment isolation** via [`soffice.py`](https://github.com/anthropics/skills/blob/main/soffice.py) ensures compatibility with sandboxed containers through socket shims and the SVP VCL plugin.
- You can integrate the functionality via **CLI**, **Python API**, or **manual subprocess** calls depending on your automation requirements.
- For read-only scenarios, **Pandoc** provides an alternative to extract change markup without modifying the source document.

## Frequently Asked Questions

### Can I accept tracked changes in DOCX files without installing LibreOffice?

No. The `anthropics/skills` implementation relies on LibreOffice's UNO API and the specific `.uno:AcceptAllTrackedChanges` dispatcher command, which is only available through the `soffice` binary. There is no pure-Python library that can natively accept tracked changes in DOCX files because the revision tracking logic requires a full office suite engine to resolve conflicts and formatting adjustments.

### How does the script handle sandboxed environments where UNIX sockets are disabled?

The [`soffice.py`](https://github.com/anthropics/skills/blob/main/soffice.py) helper detects restricted socket permissions and automatically preloads `lo_socket_shim.so` to intercept and redirect socket calls. It also sets the `SAL_USE_VCLPLUGIN=svp` environment variable to force the headless SVP (Scalable Vector Graphics Plugin) backend, ensuring LibreOffice initializes successfully in Docker containers and other sandboxed runtimes without requiring `/tmp` or abstract socket support.

### Is it possible to reject changes instead of accepting them?

Yes, though the provided script specifically implements acceptance. To reject changes programmatically, you would modify the macro URL in [`accept_changes.py`](https://github.com/anthropics/skills/blob/main/accept_changes.py) to invoke `.uno:RejectAllTrackedChanges` instead of `.uno:AcceptAllTrackedChanges`. The UNO dispatcher supports both commands, so you can create a [`reject_changes.py`](https://github.com/anthropics/skills/blob/main/reject_changes.py) variant using the same LibreOffice headless architecture and environment setup provided in the `anthropics/skills` repository.

### Can I process multiple DOCX files in batch?

Yes. The `accept_changes` function is stateless and designed for batch operations. You can iterate over a directory of DOCX files and call the function for each pair of input and output paths. Because each invocation uses a unique temporary LibreOffice profile path (configurable via the `UserInstallation` parameter), parallel processing is also safe—simply ensure each concurrent process uses a distinct profile directory to avoid file locking conflicts.