From 73447d841f9edf496e098a91bd51ff77516d699b Mon Sep 17 00:00:00 2001 From: Flavio Oliva Date: Wed, 16 Sep 2026 22:40:20 +0100 Subject: [PATCH 1/2] docs(update): name the lock holder before the upgrade, not after MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #848's three bad states came from an MCP server this repo tells people to install. An editor with gflow registered starts one `gflow mcp run` per session; they are long-lived and usually plural, and on Windows they hold files inside the install open, so a package manager can abort part-way and leave the environment broken. The advice to stop those processes already existed — in the remediation `gflow update` prints once the damage is done. Saying it in the upgrade section is the difference between a recovery step and not needing one. Also states why `gflow update` cannot pre-flight this itself: it is one of those processes. `structlog` imports `greenlet` unconditionally, so every `gflow` maps the venv's native modules, and refusing while any gflow holds the install would refuse every run. Mechanism reported with a reproduction by Cstanish127 on #824. Closes #848 --- docs/USAGE.md | 27 ++++++++++++++++++++++++--- website/docs/USAGE.md | 27 ++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index c41f2569d..a05110cc2 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1519,9 +1519,30 @@ points at the venv's python — so `gflow update` reports it as upgraded with a note quoting the manager's exit code; the next update from another shell refreshes the launcher. -Restart any running `gflow serve` / MCP server afterwards; a long-lived process -keeps the old code until it restarts. There is deliberately no MCP twin of this -command: a server must not replace its own code underneath itself. +**Stop every other `gflow` process BEFORE you upgrade — not after.** On Windows a +running `gflow` holds files inside the install open, and a package manager that +cannot replace them can abort part-way and leave the environment broken. The +holder people actually hit is **`gflow mcp run`**: an editor that has gflow +registered as an MCP server starts one per session, they are long-lived, and +there is usually more than one. Reported with a reproduction in +[#848](https://github.com/ffroliva/gflow-cli/issues/848): `uv tool install +--force` failed with *"Access is denied"* on `…\uv\tools\gflow-cli\Scripts` +and left the install answering `No module named 'gflow_cli'` until those +processes were stopped and the install re-run. + +Close the editor sessions (or stop the servers) first, and on Windows check with +`tasklist | findstr gflow` — `pgrep -f gflow` elsewhere. `gflow update` cannot +do this for you: it is itself one of those processes, so refusing to run while +any gflow holds the install would refuse every time. + +If an upgrade does abort, `gflow update` tells you rather than reporting a +version: it imports gflow-cli in a fresh isolated interpreter afterwards, and a +failed import exits 11 with a **forced reinstall** as the remediation. Run that +from a shell with no gflow running. + +Restart any running `gflow serve` / MCP server afterwards too; a long-lived +process keeps the old code until it restarts. There is deliberately no MCP twin +of this command: a server must not replace its own code underneath itself. Every command also prints an **update banner** on stderr (a one-line notice when stderr is not a terminal) when a newer release is known — see diff --git a/website/docs/USAGE.md b/website/docs/USAGE.md index 6bafd7809..e24d4f631 100644 --- a/website/docs/USAGE.md +++ b/website/docs/USAGE.md @@ -1519,9 +1519,30 @@ points at the venv's python — so `gflow update` reports it as upgraded with a note quoting the manager's exit code; the next update from another shell refreshes the launcher. -Restart any running `gflow serve` / MCP server afterwards; a long-lived process -keeps the old code until it restarts. There is deliberately no MCP twin of this -command: a server must not replace its own code underneath itself. +**Stop every other `gflow` process BEFORE you upgrade — not after.** On Windows a +running `gflow` holds files inside the install open, and a package manager that +cannot replace them can abort part-way and leave the environment broken. The +holder people actually hit is **`gflow mcp run`**: an editor that has gflow +registered as an MCP server starts one per session, they are long-lived, and +there is usually more than one. Reported with a reproduction in +[#848](https://github.com/ffroliva/gflow-cli/issues/848): `uv tool install +--force` failed with *"Access is denied"* on `…\uv\tools\gflow-cli\Scripts` +and left the install answering `No module named 'gflow_cli'` until those +processes were stopped and the install re-run. + +Close the editor sessions (or stop the servers) first, and on Windows check with +`tasklist | findstr gflow` — `pgrep -f gflow` elsewhere. `gflow update` cannot +do this for you: it is itself one of those processes, so refusing to run while +any gflow holds the install would refuse every time. + +If an upgrade does abort, `gflow update` tells you rather than reporting a +version: it imports gflow-cli in a fresh isolated interpreter afterwards, and a +failed import exits 11 with a **forced reinstall** as the remediation. Run that +from a shell with no gflow running. + +Restart any running `gflow serve` / MCP server afterwards too; a long-lived +process keeps the old code until it restarts. There is deliberately no MCP twin +of this command: a server must not replace its own code underneath itself. Every command also prints an **update banner** on stderr (a one-line notice when stderr is not a terminal) when a newer release is known — see From 91ff33dd21a649cdd95aceeaa5001df97fe0d09a Mon Sep 17 00:00:00 2001 From: Flavio Oliva Date: Thu, 17 Sep 2026 07:21:41 +0100 Subject: [PATCH 2/2] docs(update): distinguish the benign launcher lock, and warn where the holder is installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Council review of #856 converged on three findings. Three dimensions independently flagged that the new "if an upgrade does abort" paragraph restated the exit-11 / forced-reinstall paragraph fifteen lines above it, with less precision — the original also names the repair command and why a plain `upgrade` cannot do it. Replaced with a back-reference, so the mechanism is stated once and cannot drift. The block also contradicted the paragraph immediately before it: that one says a running `gflow.exe` holding its own launcher is benign and the upgrade lands anyway, this one said a running gflow leaves the environment broken. Same-sounding phenomenon, opposite guidance, adjacent. Named the distinguisher — the launcher you are running versus a different gflow holding the install's contents. `tasklist` matches image names only, so it finds the `gflow.exe` shim but not a server registered as `uv run gflow mcp run`; said so rather than implying parity with `pgrep -f`, which is now `-fa` so the command line that matched is visible before anything is stopped. Softened two frequency claims ("people actually hit", "usually more than one") that a single reproduction does not support, per the claims discipline in AGENTS.md, and pointed the citation at the comment carrying that reproduction instead of the issue body, which still carries the refuted chrome.exe attribution. docs/MCP.md is the page that installs the lock holder and never said registering it gates upgrades; one note at the end of § 4 now covers every client listed there. USAGE.md's own instruction landed 67 lines into the section, so the section head now carries a one-line pointer. --- docs/MCP.md | 6 ++++++ docs/USAGE.md | 41 +++++++++++++++++++++-------------------- website/docs/MCP.md | 6 ++++++ website/docs/USAGE.md | 41 +++++++++++++++++++++-------------------- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/docs/MCP.md b/docs/MCP.md index 227c9b80c..b6453f058 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -356,6 +356,12 @@ unauthenticated, which is the local single-user default. > `gflow serve` — it currently runs the MCP/SSE server only. See the > [CHANGELOG](../CHANGELOG.md) for the roadmap. +> **However you register it, that server then gates your upgrades.** Every client +> above starts a long-lived `gflow mcp run` — one per session — and on Windows it +> holds the install open, so a package manager can abort part-way through +> `gflow update` and leave the environment broken. Stop the servers first: +> [USAGE § `gflow update`](USAGE.md#gflow-update). + --- ## 5. Security & Anti-Bot Mitigations diff --git a/docs/USAGE.md b/docs/USAGE.md index a05110cc2..d8a37a458 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1455,6 +1455,7 @@ that privacy setting, so missing names are expected, not a defect. ## `gflow update` Upgrade gflow-cli in place, through the package manager that installed it. +**Stop every other `gflow` process first** — see below. ```text gflow update [--check] [--json] @@ -1519,26 +1520,26 @@ points at the venv's python — so `gflow update` reports it as upgraded with a note quoting the manager's exit code; the next update from another shell refreshes the launcher. -**Stop every other `gflow` process BEFORE you upgrade — not after.** On Windows a -running `gflow` holds files inside the install open, and a package manager that -cannot replace them can abort part-way and leave the environment broken. The -holder people actually hit is **`gflow mcp run`**: an editor that has gflow -registered as an MCP server starts one per session, they are long-lived, and -there is usually more than one. Reported with a reproduction in -[#848](https://github.com/ffroliva/gflow-cli/issues/848): `uv tool install ---force` failed with *"Access is denied"* on `…\uv\tools\gflow-cli\Scripts` -and left the install answering `No module named 'gflow_cli'` until those -processes were stopped and the install re-run. - -Close the editor sessions (or stop the servers) first, and on Windows check with -`tasklist | findstr gflow` — `pgrep -f gflow` elsewhere. `gflow update` cannot -do this for you: it is itself one of those processes, so refusing to run while -any gflow holds the install would refuse every time. - -If an upgrade does abort, `gflow update` tells you rather than reporting a -version: it imports gflow-cli in a fresh isolated interpreter afterwards, and a -failed import exits 11 with a **forced reinstall** as the remediation. Run that -from a shell with no gflow running. +**Stop every other `gflow` process BEFORE you upgrade — not after.** The launcher +lock just above is the benign case: it is the `gflow.exe` *you* are running, and +the upgrade lands anyway. A **different** gflow, holding the install's contents +open, is not — there the manager can abort part-way and leave the environment +broken. The holder reported so far is **`gflow mcp run`**: an editor with gflow +registered as an MCP server starts one per session, and they outlive the command +that started them. Reported with a reproduction in +[#848](https://github.com/ffroliva/gflow-cli/issues/848#issuecomment-5704903340): +`uv tool install --force` failed with *"Access is denied"* on +`…\uv\tools\gflow-cli\Scripts` and left the install answering +`No module named 'gflow_cli'` until those processes were stopped and the install +re-run. + +Close the editor sessions (or stop the servers) first. On Windows, +`tasklist | findstr gflow` lists image names only — a server registered as +`uv run gflow …` shows up as `uv.exe`, not `gflow.exe`; elsewhere +`pgrep -fa gflow` prints the command line it matched, so you can see what you +are about to stop. `gflow update` cannot run this check for you: it is itself +one of those processes. If an upgrade does abort anyway, the exit-11 **forced +reinstall** above is the repair — run it from a shell with no gflow running. Restart any running `gflow serve` / MCP server afterwards too; a long-lived process keeps the old code until it restarts. There is deliberately no MCP twin diff --git a/website/docs/MCP.md b/website/docs/MCP.md index 227c9b80c..b6453f058 100644 --- a/website/docs/MCP.md +++ b/website/docs/MCP.md @@ -356,6 +356,12 @@ unauthenticated, which is the local single-user default. > `gflow serve` — it currently runs the MCP/SSE server only. See the > [CHANGELOG](../CHANGELOG.md) for the roadmap. +> **However you register it, that server then gates your upgrades.** Every client +> above starts a long-lived `gflow mcp run` — one per session — and on Windows it +> holds the install open, so a package manager can abort part-way through +> `gflow update` and leave the environment broken. Stop the servers first: +> [USAGE § `gflow update`](USAGE.md#gflow-update). + --- ## 5. Security & Anti-Bot Mitigations diff --git a/website/docs/USAGE.md b/website/docs/USAGE.md index e24d4f631..21f5e6a53 100644 --- a/website/docs/USAGE.md +++ b/website/docs/USAGE.md @@ -1455,6 +1455,7 @@ that privacy setting, so missing names are expected, not a defect. ## `gflow update` Upgrade gflow-cli in place, through the package manager that installed it. +**Stop every other `gflow` process first** — see below. ```text gflow update [--check] [--json] @@ -1519,26 +1520,26 @@ points at the venv's python — so `gflow update` reports it as upgraded with a note quoting the manager's exit code; the next update from another shell refreshes the launcher. -**Stop every other `gflow` process BEFORE you upgrade — not after.** On Windows a -running `gflow` holds files inside the install open, and a package manager that -cannot replace them can abort part-way and leave the environment broken. The -holder people actually hit is **`gflow mcp run`**: an editor that has gflow -registered as an MCP server starts one per session, they are long-lived, and -there is usually more than one. Reported with a reproduction in -[#848](https://github.com/ffroliva/gflow-cli/issues/848): `uv tool install ---force` failed with *"Access is denied"* on `…\uv\tools\gflow-cli\Scripts` -and left the install answering `No module named 'gflow_cli'` until those -processes were stopped and the install re-run. - -Close the editor sessions (or stop the servers) first, and on Windows check with -`tasklist | findstr gflow` — `pgrep -f gflow` elsewhere. `gflow update` cannot -do this for you: it is itself one of those processes, so refusing to run while -any gflow holds the install would refuse every time. - -If an upgrade does abort, `gflow update` tells you rather than reporting a -version: it imports gflow-cli in a fresh isolated interpreter afterwards, and a -failed import exits 11 with a **forced reinstall** as the remediation. Run that -from a shell with no gflow running. +**Stop every other `gflow` process BEFORE you upgrade — not after.** The launcher +lock just above is the benign case: it is the `gflow.exe` *you* are running, and +the upgrade lands anyway. A **different** gflow, holding the install's contents +open, is not — there the manager can abort part-way and leave the environment +broken. The holder reported so far is **`gflow mcp run`**: an editor with gflow +registered as an MCP server starts one per session, and they outlive the command +that started them. Reported with a reproduction in +[#848](https://github.com/ffroliva/gflow-cli/issues/848#issuecomment-5704903340): +`uv tool install --force` failed with *"Access is denied"* on +`…\uv\tools\gflow-cli\Scripts` and left the install answering +`No module named 'gflow_cli'` until those processes were stopped and the install +re-run. + +Close the editor sessions (or stop the servers) first. On Windows, +`tasklist | findstr gflow` lists image names only — a server registered as +`uv run gflow …` shows up as `uv.exe`, not `gflow.exe`; elsewhere +`pgrep -fa gflow` prints the command line it matched, so you can see what you +are about to stop. `gflow update` cannot run this check for you: it is itself +one of those processes. If an upgrade does abort anyway, the exit-11 **forced +reinstall** above is the repair — run it from a shell with no gflow running. Restart any running `gflow serve` / MCP server afterwards too; a long-lived process keeps the old code until it restarts. There is deliberately no MCP twin