How to Debug File Locking Issues in OfficeCLI: A Complete Guide

Debug file locking issues in OfficeCLI by enabling verbose logging with --debug, inspecting the temporary .lock file under %TEMP%/officecli, verifying the ResidentServer process ID, and checking OS file handles with handle.exe or lsof before forcing stale lock removal.

When multiple processes access the same Office document through OfficeCLI, file locking conflicts can block operations or cause commands to hang indefinitely. Understanding how to debug these locking issues is essential for maintaining reliable automation workflows with the iOfficeAI/OfficeCLI repository. The tool implements a sophisticated locking mechanism through a resident server process that requires specific diagnostic approaches to troubleshoot effectively.

Understanding OfficeCLI's File Locking Architecture

OfficeCLI manages document access through a resident server pattern that maintains exclusive locks on files during editing operations. The architecture consists of several key components working together to prevent corruption while enabling concurrent access attempts.

The ResidentServer Lock Manager

The ResidentServer class in src/officecli/ResidentServer.cs serves as the central coordinator for file locks. It maintains a single command lock that protects the open document and runs a ping responder to verify lock liveness while the server idles. According to the source code comments at lines 49 and 2713, the server implements specific race-condition protections during shutdown to ensure locks release properly.

Client-Server Communication

The ResidentClient class in src/officecli/ResidentClient.cs (line 210) handles communication with the server over named pipes. The client acquires the lock before sending mutation commands, with specific handling for Windows pipe deadlock scenarios noted in the source comments.

Temporary Lock Markers

When OfficeCLI starts, the Program.cs file (line 71) creates temporary .lock and .port files under Path.GetTempPath() to mark the running server instance. These files follow the naming convention document-<hash>.lock based on the document path.

Document Handlers

WordHandler and ExcelHandler open underlying OpenXML packages with write handles that block other processes from write access. The source at src/officecli/Handlers/WordHandler.cs (lines 2242 and 2274) includes best-effort corruption handling and deferred path resolution logic.

Common Causes of File Locking Problems

Simultaneous Write Lock Contention

When two processes attempt to acquire write locks on the same document simultaneously, the first process wins while the second blocks on the OS file handle until the lock releases. This manifests as hanging commands or timeout errors.

ResidentServer Shutdown Race Conditions

If the server cancels while a command still holds the lock, the lock may release after shutdown logic completes. This creates a brief window where new clients see the lock file but the document remains busy, causing intermittent access failures.

Windows Named-Pipe Deadlocks

Under heavy load, the .NET 11 preview runtime can cause StreamReader/StreamWriter pairs on the named pipe to deadlock, as noted in the comments within ResidentServer.cs. This prevents the client from communicating with the server even when the file technically unlocks.

Stale Lock Files After Crashes

If the ResidentServer process crashes or is killed without proper cleanup, the temporary .lock file persists on disk. Subsequent OfficeCLI invocations incorrectly assume another instance holds the lock, blocking new operations indefinitely.

Step-by-Step Debugging Workflow

Enable Verbose Logging

Start diagnostics by enabling trace-level output. Set the environment variable OFFICECLI_LOG=trace or use the --debug flag to capture all lock acquisition and release events with timestamps.


# Bash/Linux

export OFFICECLI_LOG=trace
officecli --debug word replace-text --input report.docx --search "old" --replace "new"

# PowerShell

$env:OFFICECLI_LOG="trace"
officecli --debug excel add-sheet --input data.xlsx --name "Summary"

Inspect the Lock Marker File

Locate the lock file under your system temp directory (%TEMP% on Windows, /tmp on Linux). The file path derives from the document name and hash:

string docPath = @"C:\Users\you\Documents\Report.docx";
string lockFile = Path.Combine(
    Path.GetTempPath(),
    "officecli",
    $"{Path.GetFileNameWithoutExtension(docPath)}-{docPath.GetHashCode():X}.lock");

Console.WriteLine($"Lock file: {lockFile}");

Verify the file exists while the server runs and disappears after clean shutdown. If the file persists after the process exits, it indicates a stale lock.

Check the Resident Server Process

Identify the process holding the lock using system tools:


# Linux/macOS

ps -ef | grep officecli

# PowerShell

Get-Process officecli

The server PID corresponds to the process holding the OS file handle. If the process remains alive after you believe you exited, it is the source of the blocking lock.

Examine Open File Handles

Use system utilities to inspect the specific file handle preventing access:


# Windows (requires Sysinternals handle.exe)

handle.exe -a <PID> | Select-String -Pattern ".docx"

# Linux

lsof -p <PID> | grep -E "\.docx|\.xlsx|\.pptx"

Look for handles pointing to the OpenXML package file. These handles indicate which process maintains the exclusive write lock.

Force-Release Stale Locks

If the server died but the lock file remains, manually remove it only after confirming no OfficeCLI processes run:


# PowerShell

Get-Process officecli -ErrorAction SilentlyContinue | Stop-Process -Force
Remove-Item "$env:TEMP\officecli\Report-AB12CD34.lock"

# Linux

pkill -f officecli
rm /tmp/officecli/Report-AB12CD34.lock

Handle Document Protected Exceptions

The CLI throws a CliException with Code = "document_protected" when commands attempt to access locked documents. Implement retry logic with exponential backoff:

int retries = 3;
while (retries-- > 0)
{
    try
    {
        await officeCli.RunAsync("ppt add-slide", "--input", path);
        break; // Success
    }
    catch (CliException ex) when (ex.Code == "document_protected")
    {
        Console.Error.WriteLine($"Document locked – retrying in 2s...");
        await Task.Delay(2000);
    }
}

Practical Code Examples

Complete Retry Wrapper for Transient Locks

public async Task RunWithLockRetry(string command, string filePath, int maxRetries = 3)
{
    int attempt = 0;
    while (attempt < maxRetries)
    {
        try
        {
            await officeCli.RunAsync(command, "--input", filePath);
            return;
        }
        catch (CliException ex) when (ex.Code == "document_protected")
        {
            attempt++;
            if (attempt >= maxRetries) throw;
            
            Console.Error.WriteLine($"Lock contention detected (attempt {attempt}/{maxRetries})");
            await Task.Delay(1500 * attempt); // Exponential backoff
        }
    }
}

Automated Stale Lock Detection and Cleanup


# PowerShell script to clean orphaned locks

$tempDir = Join-Path $env:TEMP "officecli"
$lockFiles = Get-ChildItem $tempDir -Filter "*.lock"

foreach ($lock in $lockFiles) {
    $content = Get-Content $lock.FullName -ErrorAction SilentlyContinue
    $pid = ($content | Select-String -Pattern "PID:(\d+)").Matches.Groups[1].Value
    
    if (-not (Get-Process -Id $pid -ErrorAction SilentlyContinue)) {
        Write-Host "Removing stale lock for PID $pid"
        Remove-Item $lock.FullName -Force
    }
}

Summary

  • OfficeCLI uses a ResidentServer architecture with exclusive file locks managed through temporary .lock files in the system temp directory, implemented in ResidentServer.cs and Program.cs.
  • Enable --debug or OFFICECLI_LOG=trace to capture detailed timestamps of lock acquisition and release events for timeline analysis.
  • Locate lock files programmatically using the pattern {temp}/officecli/{filename}-{hash}.lock to verify server state and detect stale markers.
  • Use OS-specific tools (handle.exe on Windows, lsof on Linux) to inspect open file handles and identify the exact process blocking document access.
  • Implement exception handling for CliException with code document_protected to gracefully handle transient contention with retry logic.
  • Clean stale locks manually only after confirming the ResidentServer process no longer exists, then restart OfficeCLI to spawn a fresh server instance.

Frequently Asked Questions

What causes the "document_protected" error in OfficeCLI?

The document_protected error occurs when a command attempts to mutate a document that is currently locked by another OfficeCLI process or resident server. According to the source code in CommandBuilder.cs and WordHandler.cs, this exception surfaces when the OpenXML package cannot acquire the necessary write handle. This typically happens when two processes access the same file simultaneously, or when a previous operation crashed without releasing the lock properly.

Where does OfficeCLI store its lock files?

OfficeCLI stores lock files in a subdirectory named officecli under the system temporary directory—%TEMP% on Windows or /tmp on Linux and macOS. As implemented in Program.cs (line 71), the application creates .lock and .port marker files derived from the document path hash. You can locate the exact path by checking Path.GetTempPath() combined with the document filename and its hash code.

Can I delete the .lock file while OfficeCLI is running?

You should not delete the .lock file while an OfficeCLI process is running, as this file coordinates communication between the client and the resident server. Removing it while the server is active can cause command failures, orphaned processes, or document corruption. Only delete the lock file after verifying via Get-Process (PowerShell) or ps (Linux) that no OfficeCLI processes remain, indicating a stale lock from a crashed instance.

How do I prevent file locking issues when running multiple OfficeCLI commands?

To prevent locking issues, implement a retry mechanism with backoff when catching CliException with code document_protected, as shown in the source examples. Alternatively, serialize your commands through a single resident server instance rather than spawning multiple parallel processes. For CI/CD pipelines, add delays between document operations or use file-level mutexes in your calling code to ensure only one OfficeCLI instance accesses a specific document at any given time.

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 →