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
.lockfiles in the system temp directory, implemented inResidentServer.csandProgram.cs. - Enable
--debugorOFFICECLI_LOG=traceto capture detailed timestamps of lock acquisition and release events for timeline analysis. - Locate lock files programmatically using the pattern
{temp}/officecli/{filename}-{hash}.lockto verify server state and detect stale markers. - Use OS-specific tools (
handle.exeon Windows,lsofon Linux) to inspect open file handles and identify the exact process blocking document access. - Implement exception handling for
CliExceptionwith codedocument_protectedto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →