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

> Effectively debug OfficeCLI file locking issues. Learn to use debug logging, inspect lock files, verify processes, and check OS handles to resolve concurrent document access problems.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: how-to-guide
- Published: 2026-08-08

---

**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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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

# Bash/Linux

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

```

```powershell

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

```csharp
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:

```bash

# Linux/macOS

ps -ef | grep officecli

```

```powershell

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

```powershell

# Windows (requires Sysinternals handle.exe)

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

```

```bash

# 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

# PowerShell

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

```

```bash

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

```csharp
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

```csharp
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

# 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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/ResidentServer.cs) and [`Program.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.cs) and [`WordHandler.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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.