Skip to content

[Bug]: specify self upgrade leaves the uv-tool install unusable on Windows (fails to delete the running venv) #5141

Open

Description

@piknockyou

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

  1. 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
  2. Confirm specify --version reports 1.2.2 and that a newer release exists (v1.2.3).
  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

  1. github-actions commented on Oct 27, 2026

    @github-actions
    Contributor

    Bug assessment — self-upgrade-windows-breakage: Likely valid, needs reproduction · severity high


    Bug Assessment: specify self upgrade can self-break uv-tool installs on Windows

    Report (summarized)

    Issue #5141 (opened by @piknockyou) reports that on Windows, running specify self upgrade from a uv tool install can leave the tool half-uninstalled: Lib/site-packages is removed, deletion of Scripts fails with access denied (os error 5), and subsequent specify invocations fail with ModuleNotFoundError: 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 running specify process), which narrows the trigger to in-place self-upgrade execution.

    Symptom

    On Windows uv tool installs, specify self upgrade may 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

    1. Install an older release with uv tool, e.g. `uv tool install --force specify-cli --from (github.com/redacted)
    2. Confirm a newer release exists and run specify self upgrade.
    3. Observe installer failure on Windows with failed to remove directory ...\Scripts ... os error 5, then specify --version failing 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 builds uv 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 run uv tool install specify-cli --force --from (git ref) under the hood, consistent with observed behavior.
    • tests/specify_cli/test_version_detection.py:358-384 and :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 upgrade for uv-tool installs runs a forced reinstall of specify-cli from inside the same installed tool environment. On Windows, executable/file locking semantics can block deletion of in-use binaries (python.exe / scripts), so uv can remove part of the environment, then fail when deleting Scripts, 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. Route specify self upgrade to an external upgrader path that does not run from the target venv (for example, emit and execute a short bootstrap command via system Python/py or cmd that runs uv 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):

    • Conservative guardrail: detect 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.
    • Attempt best-effort rollback on installer failure by reinstalling pre-upgrade tag externally. Trade-off: adds complexity and still requires an execution context outside the locked environment.

    Files likely to change:

    • src/specify_cli/selfs/command_upgrade.py
    • src/specify_cli/_version.py
    • docs/upgrade.md
    • tests/specify_cli/test_version_execution.py
    • tests/specify_cli/selfs/test_command_upgrade.py

    Tests to add or update:

    • Windows-targeted regression test proving uv-tool self-upgrade does not attempt in-place mutation from the running venv (or exits safely with explicit manual guidance).
    • Failure-path test simulating installer delete/access-denied behavior and asserting no success messaging plus actionable recovery output.
    • Verification test for external-handoff path ensuring post-upgrade version check still validates target tag.

    Risks & Considerations

    • Behavior change for Windows uv-tool users (automatic upgrade semantics may become guarded or staged).
    • External handoff implementation must avoid command-injection risks and preserve existing timeout/error reporting contracts.
    • Need clear UX messaging to prevent confusion when upgrade becomes deferred/manual on Windows.
    • Ensure pipx and non-Windows uv-tool paths remain unchanged.

    Open Questions

    • [NEEDS CLARIFICATION: should Windows uv-tool upgrades be disabled immediately as a safety hotfix, or shipped with an external-handoff implementation in one change?]
    • [NEEDS CLARIFICATION: do maintainers want parity behavior between uv-tool and pipx on Windows, or platform-specific divergence is acceptable?]

    Generated by 🐛 Assess Bug from Labeled Issue for #5141 · copilot · gpt53codex · 23.6 AIC · ⌖ 0.611 AIC · ⊞ 24.2K · ◷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions