From 04011b730579c0f0503edc13bcbe166efe0f6411 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 22:28:37 +0200 Subject: [PATCH 1/3] packaging: say what really happens when the program is running during an upgrade Measured on Windows Server 2025 on 2026-09-25, with the window open: Chocolatey goes ahead with an upgrade and with an uninstall, and reports success. It moves the package folder aside to lib-bkp and the running copy keeps working from there, but it cannot delete that copy, so it stays - after an uninstall for good. chocolateybeforemodify.ps1 used to say the operation would not succeed, which it did. It now says Chocolatey goes ahead and names the folder to delete once the program is closed. Measured again with the new text: the path it prints is where the copy is. WinGet stops half way. The upgrade failed with "Access is denied" on the program after deleting some of the other files, and the package worked again once the upgrade ran with the window closed. A portable package carries no script, so both WinGet descriptions now say to upgrade when the program is not running. The README also names microsoft/winget-cli#6160: WinGet leaves the package's folder on PATH after an uninstall, for every package that sets ArchiveBinariesDependOnPath. Measured here with WinGet 1.29.380. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/build_packages.py | 20 ++++++++++++--- packaging/README.md | 25 +++++++++++++++---- .../tools/chocolateybeforemodify.ps1.in | 15 ++++++++--- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.github/scripts/build_packages.py b/.github/scripts/build_packages.py index 5e32861..dba9e7a 100644 --- a/.github/scripts/build_packages.py +++ b/.github/scripts/build_packages.py @@ -83,17 +83,31 @@ def how_to_start(package, feed): - """The paragraph that differs by feed: what the package gives and how to start it.""" + """The paragraph that differs by feed: what the package gives and how to start it. + + The WinGet sentences about upgrading are measured, not assumed: on Windows + Server 2025 with WinGet 1.29.380 on 2026-09-25, upgrading the window while + it was open failed with "Access is denied" and left the package folder half + removed until the upgrade ran again with the window closed. A portable + package can carry no script to warn at that moment, so the description is + the only place a person can read it first. + """ other = next(p for p in PACKAGES if p is not package) other_id = other.winget_id if feed == "winget" else other.choco_id if package.kind == "cli": - return ("This package is the command line, for scripts and pipelines. The desktop " + text = ("This package is the command line, for scripts and pipelines. The desktop " "window is the package %s. Type tfg help to see the commands." % other_id) + if feed == "winget": + text += (" Upgrade it when no tfg run is in progress. WinGet cannot replace a " + "program while it runs.") + return text if feed == "winget": return ("This package is the desktop window. The command line is the package %s. " "WinGet adds no Start menu shortcut for it. Open a new terminal and type " "tfg-gui. The window offers a tfg-out folder in the directory it was started " - "from." % other_id) + "from. Close the window before you upgrade. WinGet cannot replace a running " + "program, so it stops half way, and the package works again once the " + "upgrade runs with the window closed." % other_id) return ("This package is the desktop window. The command line is the package %s. It " "adds a Start menu shortcut and the tfg-gui command. Started from the shortcut, " "the window offers a tfg-out folder in your user profile." % other_id) diff --git a/packaging/README.md b/packaging/README.md index cc7acb4..6e6a262 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -35,7 +35,11 @@ the real file. With it, WinGet makes no link and puts the package's folder on `PATH`. The command line would work either way, but without the field its shape depends on the machine - a link where symbolic links are allowed, the folder on `PATH` where they are not. WinGet adds no Start menu shortcut for a portable -package, and the window's description says so. +package, and the window's description says so. One cost of the field is not +ours to fix: WinGet leaves the package's folder on `PATH` after an uninstall - +[microsoft/winget-cli#6160](https://github.com/microsoft/winget-cli/issues/6160), +open, and true of every package that sets it. Measured here with WinGet +1.29.380: the entry stays, pointing at a folder that no longer exists. **Chocolatey.** The package downloads the release archive rather than carrying it, so it holds no binaries and owes no `VERIFICATION.txt`, and the archive is the @@ -50,10 +54,21 @@ into the package. The icon is a jsDelivr address pinned to the release tag: moderation refuses `raw.githubusercontent.com` and `github.com/.../raw` alike, and an icon on a branch would keep changing under an approved package. -**Neither package ends a running program.** `chocolateybeforemodify.ps1` says when -the program is still running from the package, and leaves closing it to the -person - a run in progress may be halfway through a set of files, and cutting it -would leave files with no manifest to say what they are. +**Neither package ends a running program** - a run in progress may be halfway +through a set of files, and cutting it would leave files with no manifest to say +what they are. What each feed does instead was measured on Windows Server 2025 +on 2026-09-25, with the window open: + +- **Chocolatey goes ahead**, an upgrade and an uninstall alike, and reports + success. It moves the package folder aside to `lib-bkp`, and the running copy + keeps working from there - but it cannot delete that copy, so the copy stays: + after an upgrade until the next Chocolatey operation on the package, after an + uninstall for good. `chocolateybeforemodify.ps1` says so at that moment and + names the folder to delete once the program is closed. +- **WinGet stops half way.** An upgrade fails with "Access is denied" on the + program, having already deleted some of the other files, and the package works + again once the upgrade runs with the window closed. A portable package carries + no script, so the description is where this is said. ## Submitting diff --git a/packaging/chocolatey/tools/chocolateybeforemodify.ps1.in b/packaging/chocolatey/tools/chocolateybeforemodify.ps1.in index 88b79ba..3d2a8c5 100644 --- a/packaging/chocolatey/tools/chocolateybeforemodify.ps1.in +++ b/packaging/chocolatey/tools/chocolateybeforemodify.ps1.in @@ -2,8 +2,15 @@ # # Chocolatey runs this from the INSTALLED package before an upgrade or an # uninstall. It says so when the program is still running from this package, -# because Windows does not let the files of a running program be replaced, and -# a person should hear why before the operation that follows complains. +# and it says what will really happen, which was measured rather than assumed +# (Windows Server 2025, docs/PACKAGING-2026-09-25.md section 10). It does not +# fail: Chocolatey moves the package folder aside to lib-bkp, goes ahead and +# reports success, and the running copy keeps working from there. What it +# cannot do is delete that copy while it runs, so the copy stays in lib-bkp - +# after an upgrade until the next Chocolatey operation on this package, and +# after an uninstall for good, because there may be no next operation. The +# first version of this message said the operation would fail, which it did +# not. # # It never ends the program. That is the owner's decision of 2026-09-25: a run # in progress may be halfway through writing a set of files, and cutting it @@ -28,5 +35,7 @@ try { } if ($running.Count -gt 0) { - Write-Warning '{{TITLE}} is running from this package. Close it before you upgrade or remove the package. Windows does not let the files of a running program be replaced.' + $leftover = Join-Path $env:ChocolateyInstall 'lib-bkp\{{CHOCO_ID}}' + Write-Warning '{{TITLE}} is running from this package. Chocolatey goes ahead, but it cannot delete the running copy, so that copy stays behind.' + Write-Warning "It is left in $leftover. Close the program, then delete that folder." } From ffc478f167349e18dae68cf41334fcbeca29f2b7 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 22:49:27 +0200 Subject: [PATCH 2/3] packaging: the WinGet sentences say only what was measured, and the command line is now measured too An outside review of #144 said two sentences claimed more than was measured: the command line's upgrade warning was inferred from the window, and the README presented an issue with one reproduction as true of every package. The command line is now measured. On Windows 11 in user scope, with a tfg run held in progress, the upgrade failed with "Access is denied" and left only the program and the notices in the folder, and ran through once the run ended - the same as the window. The window measurement was repeated there with the same result, so the description now says what happens rather than a general rule. The PATH entry left after an uninstall was reproduced in user scope on Windows 11 as well as in machine scope on Windows Server 2025, for both packages, with WinGet 1.29.380. The README says that, and describes microsoft/winget-cli#6160 as a report of the same for another package. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/build_packages.py | 19 +++++++++++-------- packaging/README.md | 18 +++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/scripts/build_packages.py b/.github/scripts/build_packages.py index dba9e7a..73f71a1 100644 --- a/.github/scripts/build_packages.py +++ b/.github/scripts/build_packages.py @@ -85,12 +85,14 @@ def how_to_start(package, feed): """The paragraph that differs by feed: what the package gives and how to start it. - The WinGet sentences about upgrading are measured, not assumed: on Windows - Server 2025 with WinGet 1.29.380 on 2026-09-25, upgrading the window while - it was open failed with "Access is denied" and left the package folder half - removed until the upgrade ran again with the window closed. A portable - package can carry no script to warn at that moment, so the description is - the only place a person can read it first. + The WinGet sentences about upgrading are measured, not assumed, with WinGet + 1.29.380 on 2026-09-25: on Windows Server 2025 in machine scope and on + Windows 11 in user scope, upgrading the window while it was open failed + with "Access is denied" and left the package folder half removed until the + upgrade ran again with the window closed. On Windows 11 the command line + did the same while a tfg run was in progress. A portable package can carry + no script to warn at that moment, so the description is the only place a + person can read it first. """ other = next(p for p in PACKAGES if p is not package) other_id = other.winget_id if feed == "winget" else other.choco_id @@ -98,8 +100,9 @@ def how_to_start(package, feed): text = ("This package is the command line, for scripts and pipelines. The desktop " "window is the package %s. Type tfg help to see the commands." % other_id) if feed == "winget": - text += (" Upgrade it when no tfg run is in progress. WinGet cannot replace a " - "program while it runs.") + text += (" Upgrade it when no tfg run is in progress. WinGet cannot replace " + "tfg while it runs, so it stops half way, and the package works again " + "once the upgrade runs with no run in progress.") return text if feed == "winget": return ("This package is the desktop window. The command line is the package %s. " diff --git a/packaging/README.md b/packaging/README.md index 6e6a262..4523e76 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -36,10 +36,12 @@ the real file. With it, WinGet makes no link and puts the package's folder on depends on the machine - a link where symbolic links are allowed, the folder on `PATH` where they are not. WinGet adds no Start menu shortcut for a portable package, and the window's description says so. One cost of the field is not -ours to fix: WinGet leaves the package's folder on `PATH` after an uninstall - +ours to fix. WinGet 1.29.380 left the package's folder on `PATH` after an +uninstall, for both of these packages, in machine scope on Windows Server 2025 +and in user scope on Windows 11 - measured on 2026-09-25, the entry stays and +points at a folder that no longer exists. [microsoft/winget-cli#6160](https://github.com/microsoft/winget-cli/issues/6160), -open, and true of every package that sets it. Measured here with WinGet -1.29.380: the entry stays, pointing at a folder that no longer exists. +open, reports the same for another package that sets the field. **Chocolatey.** The package downloads the release archive rather than carrying it, so it holds no binaries and owes no `VERIFICATION.txt`, and the archive is the @@ -56,8 +58,9 @@ and an icon on a branch would keep changing under an approved package. **Neither package ends a running program** - a run in progress may be halfway through a set of files, and cutting it would leave files with no manifest to say -what they are. What each feed does instead was measured on Windows Server 2025 -on 2026-09-25, with the window open: +what they are. What each feed does instead was measured on 2026-09-25, with the +program running - Chocolatey on Windows Server 2025, WinGet there in machine +scope and on Windows 11 in user scope: - **Chocolatey goes ahead**, an upgrade and an uninstall alike, and reports success. It moves the package folder aside to `lib-bkp`, and the running copy @@ -67,8 +70,9 @@ on 2026-09-25, with the window open: names the folder to delete once the program is closed. - **WinGet stops half way.** An upgrade fails with "Access is denied" on the program, having already deleted some of the other files, and the package works - again once the upgrade runs with the window closed. A portable package carries - no script, so the description is where this is said. + again once the upgrade runs with the program closed. Measured for the window + and, on Windows 11, for the command line with a run in progress. A portable + package carries no script, so the description is where this is said. ## Submitting From 7cde8daad686528125b44bc6f4e4b30b0a4edec2 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 23:18:01 +0200 Subject: [PATCH 3/3] packaging: "when tfg is not running" instead of a command that does not exist The command line's WinGet description said "when no tfg run is in progress", and tfg has no run command - an outside review of #144 read it as one. It proposed naming tfg generate, but any tfg command holds the program file while it runs, so the sentence now says when tfg is not running. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/build_packages.py | 11 +++++++---- packaging/README.md | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/scripts/build_packages.py b/.github/scripts/build_packages.py index 73f71a1..e503b14 100644 --- a/.github/scripts/build_packages.py +++ b/.github/scripts/build_packages.py @@ -90,7 +90,7 @@ def how_to_start(package, feed): Windows 11 in user scope, upgrading the window while it was open failed with "Access is denied" and left the package folder half removed until the upgrade ran again with the window closed. On Windows 11 the command line - did the same while a tfg run was in progress. A portable package can carry + did the same while a tfg command was running. A portable package can carry no script to warn at that moment, so the description is the only place a person can read it first. """ @@ -100,9 +100,12 @@ def how_to_start(package, feed): text = ("This package is the command line, for scripts and pipelines. The desktop " "window is the package %s. Type tfg help to see the commands." % other_id) if feed == "winget": - text += (" Upgrade it when no tfg run is in progress. WinGet cannot replace " - "tfg while it runs, so it stops half way, and the package works again " - "once the upgrade runs with no run in progress.") + # "when tfg is not running" rather than naming a command: any tfg + # command holds the program file while it runs, and "a tfg run" read + # as a command that does not exist (outside review of #144). + text += (" Upgrade it when tfg is not running. WinGet cannot replace tfg while " + "it runs, so it stops half way, and the package works again once the " + "upgrade runs after tfg has finished.") return text if feed == "winget": return ("This package is the desktop window. The command line is the package %s. " diff --git a/packaging/README.md b/packaging/README.md index 4523e76..d6cb146 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -71,7 +71,7 @@ scope and on Windows 11 in user scope: - **WinGet stops half way.** An upgrade fails with "Access is denied" on the program, having already deleted some of the other files, and the package works again once the upgrade runs with the program closed. Measured for the window - and, on Windows 11, for the command line with a run in progress. A portable + and, on Windows 11, for the command line while a tfg command was running. A portable package carries no script, so the description is where this is said. ## Submitting