Repository navigation
[Bug]: specify self upgrade leaves the uv-tool install unusable on Windows (fails to delete the running venv) #5141
Description
Bug Description
On Windows, specify self upgrade breaks the installation it is upgrading. It resolves the new release and runs uv tool install specify-cli --force --from git+...@<tag> as a child process, but that child runs from inside the tool's own environment. uv removes the environment directory, deletes Lib\site-packages first, then fails on Scripts - the environment's python.exe is the running interpreter, and Windows will not delete a running executable. What is left is a half-deleted environment: the specify shim still exists, the package does not.
Steps to Reproduce
- On Windows with uv, install an older release:
uv tool install --force specify-cli --from git+https://github.com/github/spec-kit.git@v1.2.2 - Confirm
specify --versionreports1.2.2and that a newer release exists (v1.2.3). - Run
specify self upgrade.
Expected Behavior
The CLI is upgraded to v1.2.3 and keeps working.
Actual Behavior
The installer fails and the CLI is left unusable.
specify --version afterwards:
ModuleNotFoundError: No module named 'specify_cli'
uv tool list afterwards:
Failed find package `specify-cli` in tool environment
The tool environment keeps only its shell:
<tool dir>\specify-cli\
pyvenv.cfg
Scripts\ (python.exe, pythonw.exe, specify.exe, ...)
uv-receipt.toml (still rev=v1.2.2)
Specify CLI Version
1.2.2 (upgrade target v1.2.3)
AI Agent
Not applicable
Operating System
Windows 10 Pro 22H2 (build 19045)
Python Version
3.14.8 (uv-managed)
Error Logs
Upgrading specify-cli 1.2.2 → v1.2.3 via uv tool: <uv.exe> tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@v1.2.3
Resolved 43 packages in 66ms
error: failed to remove directory
`<tool dir>\specify-cli\Scripts`: Zugriff verweigert (os error 5)
Upgrade failed. Installer exit code: 2.
Additional Context
Running the same installer from a separate shell, not from inside the tool, succeeds and leaves a healthy installation:
uv tool install --force specify-cli --from git+https://github.com/github/spec-kit.git@v1.2.3
So the failure is specific to running the installer from the tool's own process.
specify self upgrade when already on the latest release is a no-op and does not break anything - the problem only appears when there is an actual upgrade to run.
UV_TOOL_DIR on this machine points to a scoop-managed directory; the failing directory is the tool's own venv, so the location should not be relevant to the failure.
The same Windows limitation is described in another uv-tool project, with the conclusion that an external installer is the working upgrade path: microsoft/conductor#171
AI Disclosure
Created with opencode (DeepSeek V4.1 Flash), human-supervised: the agent reproduced the failure on the reporter's machine, collected the logs and the after-state, and drafted this report; the reporter reviewed and submitted it.
Activity
github-actions commentedon Oct 27, 2026
Bug assessment — self-upgrade-windows-breakage: Likely valid, needs reproduction · severity high
Bug Assessment:
specify self upgradecan self-break uv-tool installs on Windowsspecify self upgradeleaves the uv-tool install unusable on Windows (fails to delete the running venv) #5141Report (summarized)
Issue #5141 (opened by @piknockyou) reports that on Windows, running
specify self upgradefrom auv toolinstall can leave the tool half-uninstalled:Lib/site-packagesis removed, deletion ofScriptsfails with access denied (os error 5), and subsequentspecifyinvocations fail withModuleNotFoundError: No module named 'specify_cli'.The reporter provides a concrete before/after state, exact command sequence, and installer error output. They also note the same
uv tool install --force --from ...command succeeds when executed externally (not from the runningspecifyprocess), which narrows the trigger to in-place self-upgrade execution.Symptom
On Windows
uv toolinstalls,specify self upgrademay invoke a forced reinstall against the same environment that hosts the currently running interpreter, and the uninstall/reinstall phase can fail mid-flight when deleting live executables. Expected behavior is a successful upgrade or a safe rollback that leaves the CLI usable.Reproduction
specify self upgrade.failed to remove directory ...\Scripts ... os error 5, thenspecify --versionfailing with missing module.[NEEDS CLARIFICATION: exact uv version and whether the failure reproduces on Windows 11/current uv builds as well as Windows 10 22H2.]
Suspected Code Paths
src/specify_cli/selfs/command_upgrade.py:171-197— orchestrates upgrade by calling_run_installer(plan)and exits immediately on non-zero installer return code; no Windows-specific guard/alternate path before mutating install.src/specify_cli/_version.py:631-648(_assemble_installer_argv) — for_InstallMethod.UV_TOOL, always buildsuv tool install specify-cli --force --from <git+...``@tag``>.src/specify_cli/_version.py:484-606(_detect_install_method) +:183-194(_INSTALLER_PATH_PREFIXES) — explicitly classifies uv-tool installs, including%LOCALAPPDATA%\uv\tools\specify-cli\on Windows, so this path is expected to run there.src/specify_cli/_version.py:771-846(_run_installer) — runs installer synchronously as a child of the currently running process using the assembled argv; no detachment/handoff to an external bootstrap process on Windows.docs/upgrade.md:46— documents that uv-tool upgrades runuv tool install specify-cli --force --from (git ref)under the hood, consistent with observed behavior.tests/specify_cli/test_version_detection.py:358-384and:399-414— cover happy path / already-latest behavior, but there is no coverage for Windows locked-venv failure/partial uninstall during self-upgrade.Root Cause Hypothesis
specify self upgradefor uv-tool installs runs a forced reinstall ofspecify-clifrom inside the same installed tool environment. On Windows, executable/file locking semantics can block deletion of in-use binaries (python.exe/ scripts), souvcan remove part of the environment, then fail when deletingScripts, leaving a broken install. The current flow treats installer non-zero exit as terminal failure but does not provide a transactional safeguard, external upgrader handoff, or automatic recovery for this class of partial mutation. Confidence: high.Proposed Remediation
Preferred: For Windows +
_InstallMethod.UV_TOOL, avoid in-process destructive upgrade. Routespecify self upgradeto an external upgrader path that does not run from the target venv (for example, emit and execute a short bootstrap command via system Python/pyorcmdthat runsuv tool install --force --from ...after the current process exits), and only report success after post-upgrade verification from a fresh process. If safe external handoff cannot be guaranteed, fail closed with explicit guidance to run the manual command from a separate shell before mutating anything.This preserves current behavior on non-Windows platforms while preventing self-corruption on Windows uv-tool installs.
Alternatives (optional):
os.name == "nt"with uv-tool method and skip automatic upgrade entirely, printing manual external command steps. Trade-off: less convenient, but safest immediate mitigation.Files likely to change:
src/specify_cli/selfs/command_upgrade.pysrc/specify_cli/_version.pydocs/upgrade.mdtests/specify_cli/test_version_execution.pytests/specify_cli/selfs/test_command_upgrade.pyTests to add or update:
Risks & Considerations
Open Questions
fix(cli): preview the Windows uv tool outcome in self upgrade --dry-run