{"record":{"id":"5e231d4bb4ca43ba","repo":"iOfficeAI/OfficeCLI","slug":"another-watch-process-is-already-running-url-for","errorCode":null,"errorMessage":"Another watch process is already running{url} for {_filePath}","messagePattern":"Another watch process is already running(.+?) for (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/Watch/WatchServer.cs","lineNumber":316,"sourceCode":"\n    private void DeleteMarker()\n    {\n        try\n        {\n            var markerPath = GetWatchMarkerPath(_filePath);\n            if (File.Exists(markerPath)) File.Delete(markerPath);\n        }\n        catch { /* best-effort cleanup */ }\n    }\n\n    public async Task RunAsync(CancellationToken externalToken = default)\n    {\n        // Prevent duplicate watch processes for the same file\n        var existingPort = GetExistingWatchPort(_filePath);\n        if (existingPort.HasValue)\n        {\n            var url = existingPort.Value > 0 ? $\" at http://localhost:{existingPort.Value}\" : \"\";\n            throw new InvalidOperationException($\"Another watch process is already running{url} for {_filePath}\");\n        }\n\n        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, externalToken);\n        var token = linkedCts.Token;\n\n        _tcpListener.Start();\n        // --port 0 asks the OS for an ephemeral port; resolve the real one\n        // before it reaches the marker file and the printed URL, otherwise\n        // both would say 0 and per-file discovery (mark/goto/unwatch,\n        // IsWatching) breaks.\n        if (_port == 0)\n            _port = ((IPEndPoint)_tcpListener.LocalEndpoint!).Port;\n        WriteMarker();\n        Console.WriteLine($\"Watch: http://localhost:{_port}\");\n        Console.WriteLine($\"Watching: {_filePath}\");\n        Console.WriteLine(\"Press Ctrl+C to stop.\");\n\n        // Hook graceful shutdown signals. Cooperatively terminating a","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/Watch/WatchServer.cs#L298-L334","documentation":"Thrown by WatchServer.RunAsync when a watch is already running for the same file (GetExistingWatchPort returns a port from the marker file). It prevents two watch processes racing on one file. The message includes the existing URL when the port is positive, so the user can reuse the running watch instead of starting a duplicate.","triggerScenarios":"Starting a second watch for a file that already has a live watch process; a previous watch left a stale marker file pointing at a still-bound port.","commonSituations":"Forgetting a watch is already running; a stuck/stale marker after an unclean shutdown; automation spawning parallel watches for the same file.","solutions":["Reuse the existing watch — open/connect to the URL printed in the message.","Stop the running watch first (unwatch / SendClose / Ctrl+C), then start a new one.","If the marker is stale (no real watch bound), remove the marker file and retry."],"exampleFix":"# before: second `watch file.docx` while one runs\n# after: reuse the running watch\n#   open the URL from the error, or stop it first:\nunwatch file.docx   # then re-run watch","handlingStrategy":"validation","validationCode":"// Check before starting a watch\nint? port = WatchServer.GetExistingWatchPort(filePath);\nif (port.HasValue)\n    Console.WriteLine($\"already watching at http://localhost:{port.Value}\");","typeGuard":null,"tryCatchPattern":"try { await server.RunAsync(token); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already running\"))\n{ /* reuse the existing watch URL, or SendClose then retry */ }","preventionTips":["Always check GetExistingWatchPort/IsWatching before starting a watch.","Clean up watch processes explicitly on exit to avoid stale markers."],"tags":["watch","concurrency","duplicate-guard","ipc"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}