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

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, 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 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:

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:

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 helpers to construct custom subprocess calls:

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:

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 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 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 script in anthropics/skills automates this by deploying a Basic macro that invokes .uno:AcceptAllTrackedChanges.
  • Environment isolation via 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 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 to invoke .uno:RejectAllTrackedChanges instead of .uno:AcceptAllTrackedChanges. The UNO dispatcher supports both commands, so you can create a 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.

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 →