Debugging OfficeCLI Watch Mode Connection Issues: A Complete Troubleshooting Guide

TLDR: OfficeCLI watch mode connection failures typically stem from named pipe mismatches, stale marker files in the temp directory, or idle timeouts shutting down the server; resolve them by using absolute canonical paths, deleting orphaned .port markers, and adjusting the OFFICECLI_WATCH_IDLE_SECONDS environment variable.

The watch command in the iOfficeAI/OfficeCLI repository launches a lightweight preview server that keeps your browser synchronized with document changes through two distinct communication channels. The named pipe handles CLI-to-server messages (mutations, scroll requests), while a TCP SSE stream delivers HTML updates to the browser. When either channel breaks, the live preview stops updating or the watch process exits unexpectedly. This guide provides concrete debugging steps based on the actual implementation in src/officecli/Core/Watch/WatchServer.cs and WatchNotifier.cs.

How Watch Mode Communication Works

Understanding the dual-channel architecture is essential for effective debugging:

  • Named Pipe Channel: The WatchNotifier client sends Notify, Scroll, Mark, and Selection messages to the WatchServer via a platform-specific pipe name derived from the document path.
  • TCP SSE Channel: The server opens a random loopback port and streams Server-Sent Events to the browser preview page.
  • Process Isolation: As noted by the CONSISTENCY(watch-isolation) comment in the source, the watch process never opens the document file directly; the calling CLI command performs all rendering and forwards HTML through the pipe.

When these channels desynchronize or fail to initialize, you encounter the connection issues described below.

1. Resolve Pipe-Name Mismatches and Case Sensitivity

The GetWatchPipeName method in WatchServer.cs generates the pipe identifier from the absolute file path, normalizing it to uppercase on Windows and macOS while leaving it case-sensitive on Linux. If the CLI and watch process resolve the path differently (symbolic links, relative paths, or mixed-case on Linux), they compute different pipe names, causing NamedPipeClientStream.Connect to time out.

Verify the calculated pipe name:


# Calculate the expected pipe name for your document

dotnet script -e "using OfficeCli.Core; Console.WriteLine(WatchServer.GetWatchPipeName(Path.GetFullPath(\"mydoc.docx\")));"

Compare this output against the "pipe name" entry in the watch process logs. If they differ, the client cannot locate the server.

Fix: Always invoke officecli watch with an absolute, canonical path:

officecli watch "$(realpath mydoc.docx)"

2. Clear Stale Watch Marker Files

When starting, the watch server writes a marker file to Path.GetTempPath() using the pattern GetWatchPipeName(filePath) + ".port". This file stores the PID and TCP port. If the watch process crashes or is killed without cleaning up, the stale marker causes subsequent officecli watch invocations to believe another instance is running (see GetExistingWatchPort in WatchServer.cs).

Check for orphaned markers:


# List all OfficeCLI watch markers

ls -la "$(mktemp -d -p /tmp officecli-watch-*)" 2>/dev/null || find /tmp -name "officecli-watch-*.port" 2>/dev/null

Programmatically verify the marker:

using OfficeCli.Core;

string markerPath = WatchServer.GetWatchMarkerPath("mydoc.docx");
if (File.Exists(markerPath))
{
    var lines = File.ReadAllLines(markerPath);
    Console.WriteLine($"Stale PID={lines[0]}, Port={lines[1]}");
    File.Delete(markerPath); // Remove stale marker
}

Resolution: Delete the stale .port file manually, or ensure clean shutdowns using Ctrl+C or officecli unwatch <file>.

3. Prevent Idle Timeout Shutdowns Before Browser Connects

The watch server implements an idle timeout (default 300 seconds) configured via OFFICECLI_WATCH_IDLE_SECONDS. If no commands arrive within this window, the watchdog cancels TcpListener.AcceptTcpClientAsync and shuts down, leaving the browser with a broken SSE connection.

Detection: Look for this log line in the watch process output:


Watch idle timeout – shutting down after 300 seconds of inactivity.

Mitigation: Extend the timeout during debugging sessions:

export OFFICECLI_WATCH_IDLE_SECONDS=1800   # 30 minutes

officecli watch mydoc.docx

Alternatively, issue a no-op command like officecli view html mydoc.docx periodically to reset the timer.

4. Resolve TCP Port Collisions

WatchServer.cs selects a random free port on the loopback interface. If a previous watch process became a zombie or another application occupies the selected port, TcpListener.Start throws a SocketException with "Address already in use".

Fix: Terminate any zombie processes:

pkill -f "officecli watch"

Or manually specify a port using the hidden environment variable:

export OFFICECLI_WATCH_PORT=5000
officecli watch mydoc.docx

5. Increase Pipe Connection Timeouts

The WatchNotifier client attempts rapid connections with aggressive timeouts: 100 ms for notify operations and 200 ms for scroll, mark, or selection queries. If the watch process is still initializing (e.g., loading a heavy document), the pipe may not be ready, causing silent message drops.

Fix: Increase the timeout via environment variable before running CLI commands:

export OFFICECLI_WATCH_PIPE_TIMEOUT=5   # 5 seconds

officecli watch mydoc.docx

This gives the server adequate time to initialize its NamedPipeServerStream before the client attempts client.Connect().

6. Configure Host Allowlist for Browser SSE Connections

For anti-DNS-rebinding protection, WatchServer.cs restricts HTTP requests to Host: localhost or 127.0.0.1 by default. If you override OFFICECLI_WATCH_ALLOWED_HOSTS with a typo or attempt to access via a different hostname, the browser receives a 403 error and the SSE connection fails.

Verification: Check the browser console for:


The officecli watch preview only accepts Host: localhost / 127.0.0.1 (anti-DNS-rebinding).

Resolution: Use the default restriction, or correctly specify allowed hosts:

export OFFICECLI_WATCH_ALLOWED_HOSTS=localhost,127.0.0.1
officecli watch mydoc.docx

Practical Debugging Walkthrough

Use this systematic approach to isolate connection issues:


# 1. Start with verbose environment settings

export OFFICECLI_WATCH_IDLE_SECONDS=600
export OFFICECLI_WATCH_PIPE_TIMEOUT=5
officecli watch "$(realpath mydoc.docx)" &
WATCH_PID=$!

# 2. Verify pipe name and marker file location

dotnet script -e "using OfficeCli.Core; var fp=Path.GetFullPath(\"mydoc.docx\"); 
Console.WriteLine(\$\"Pipe: {WatchServer.GetWatchPipeName(fp)}\"); 
Console.WriteLine(\$\"Marker: {WatchServer.GetWatchMarkerPath(fp)}\");"

# 3. Force a pipe notification to test connectivity

officecli add mydoc.docx /paragraph[1] text="connection test"

# 4. Verify browser receives the update

#    → Navigate to http://localhost:<port> from the startup log

#    → Check DevTools Console for SSE "message" events

# 5. If connection drops, inspect logs for idle timeout or socket errors

tail -f /tmp/officecli-watch-*.log 2>/dev/null

# 6. Clean shutdown

kill $WATCH_PID  # or: officecli unwatch mydoc.docx

Sending Custom Pipe Messages via C#

For advanced debugging, interact with the pipe directly using WatchNotifier:

using OfficeCli.Core;

// Attempt to scroll to a specific anchor
var result = WatchNotifier.TryScroll(
    filePath: "mydoc.docx",
    selector: "#section-2");

switch (result)
{
    case ScrollResult.Ok:
        Console.WriteLine("Scroll command succeeded");
        break;
    case ScrollResult.NoWatch:
        Console.WriteLine("No active watch process found");
        break;
    case ScrollResult.NotFound msg:
        Console.WriteLine($"Anchor not found: {msg}");
        break;
}

This bypasses the CLI and verifies whether the named pipe channel is functional independent of command-line argument parsing.

Summary

  • Use absolute canonical paths when starting officecli watch to prevent case-sensitivity mismatches in pipe names generated by GetWatchPipeName.
  • Delete stale .port marker files from Path.GetTempPath() if the watch process previously crashed, blocking new instances via GetExistingWatchPort.
  • Extend OFFICECLI_WATCH_IDLE_SECONDS (default 300) to prevent premature shutdown during debugging.
  • Set OFFICECLI_WATCH_PIPE_TIMEOUT to 5 seconds or higher if the watch process is slow to initialize and causing client timeouts.
  • Verify OFFICECLI_WATCH_ALLOWED_HOSTS if the browser shows SSE connection failures, ensuring only localhost or 127.0.0.1 are permitted.
  • Check ResidentServer.cs interactions if the ping channel affects watch stability during idle-autosave cycles.

Frequently Asked Questions

Why does OfficeCLI report "No watch process" even when the server appears to be running?

This occurs when the WatchNotifier client computes a different pipe name than the WatchServer instance, typically due to case-sensitivity differences on Linux or symbolic link resolution mismatches. Run dotnet script to print WatchServer.GetWatchPipeName(Path.GetFullPath("yourfile.docx")) from both the client and server contexts to verify they match exactly.

How do I prevent the watch server from shutting down while I'm debugging?

Set the environment variable OFFICECLI_WATCH_IDLE_SECONDS to a high value (e.g., 1800 for 30 minutes) before starting the watch. The default 5-minute timeout in WatchServer.cs is designed for production use; extending it keeps the TcpListener alive during extended debugging sessions.

Can I run multiple watch processes for different documents simultaneously?

Yes, provided each document path generates a unique pipe name via GetWatchPipeName and obtains a unique TCP port. If you encounter port collisions, export OFFICECLI_WATCH_PORT with a specific free port for each instance to override the random selection logic in WatchServer.Start.

Why does my browser show "EventSource failed to load" immediately after connecting?

This typically indicates a host allowlist rejection or an idle timeout shutdown. Check the browser console for the anti-DNS-rebinding message (indicating OFFICECLI_WATCH_ALLOWED_HOSTS misconfiguration) or the terminal logs for "Watch idle timeout". Also verify no firewall or proxy is blocking the loopback SSE stream on the randomly selected TCP port.

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 →