# Debugging OfficeCLI Watch Mode Connection Issues: A Complete Troubleshooting Guide

> Troubleshoot OfficeCLI watch mode connection issues caused by named pipes, temp files, or idle timeouts. Learn to fix connection failures with this complete guide.

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

---

**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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/Watch/WatchServer.cs) and [`WatchNotifier.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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:**

```bash

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

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

**Check for orphaned markers:**

```bash

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

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

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

```bash
pkill -f "officecli watch"

```

Or manually specify a port using the hidden environment variable:

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

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

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

```bash

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

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