Skip to content

[Bug]: specify self upgrade reports "unsupported" for uv tool installs under XDG_DATA_HOME (and likely for current pipx installs) #5095

Open

Description

@hnipps

Bug Description

specify self upgrade decides whether the CLI was installed with uv tool or pipx by checking whether the resolved entrypoint lies under a fixed prefix (src/specify_cli/_version.py:177-190). The prefixes are ~/.local/share/uv/tools/specify-cli/ and ~/.local/pipx/venvs/specify-cli/, plus %LOCALAPPDATA% forms. _expand_prefix (_version.py:339-355) only expands ~ and %LOCALAPPDATA%, so XDG_DATA_HOME, UV_TOOL_DIR and PIPX_HOME are never taken into account.

The registry fallback (uv tool list / pipx list --json) only runs when the entrypoint path does not exist (_tier3_registry_lookup_allowed, _version.py:405-407). For a real install the entrypoint always exists, so the fallback never runs. An install in a relocated tool directory is therefore classified as unsupported. self upgrade then prints guidance and exits 0 without upgrading.

I verified that uv relocates its tool directory: uv tool dir prints ~/.local/share/uv/tools, and XDG_DATA_HOME=/x uv tool dir prints /x/uv/tools. I believe pipx 1.3 and later defaults to ~/.local/share/pipx on Linux for new installs, but I did not verify this because pipx was not installed in my environment.

Steps to Reproduce

From a fresh clone, after uv sync, save the script below and run uv run python -I repro.py from the repository root.

The script turns off the editable-install check (_editable_marker_seen). uv sync installs specify-cli in editable mode, so without this the check would report the venv as a source checkout before the path checks are reached. Turning it off models a normal, non-editable uv tool or pipx install. The script uses a temporary HOME holding ~/.local/bin/specify as a symlink into each tool directory layout.

import os, tempfile
from pathlib import Path
from specify_cli import _version as v

# This checkout's venv has specify-cli installed editable, which tier 2
# would report as a source checkout. Turn that check off to model a
# normal (non-editable) uv tool or pipx install.
v._editable_marker_seen = lambda: False

home = Path(tempfile.mkdtemp())
os.environ["HOME"] = str(home)
link = home / ".local" / "bin" / "specify"
link.parent.mkdir(parents=True)

for label, root in [
    ("uv tool, default dir", ".local/share/uv/tools"),
    ("uv tool, XDG_DATA_HOME=~/xdg", "xdg/uv/tools"),
    ("pipx, legacy ~/.local/pipx", ".local/pipx/venvs"),
    ("pipx, ~/.local/share/pipx", ".local/share/pipx/venvs"),
]:
    exe = home / root / "specify-cli" / "bin" / "specify"
    exe.parent.mkdir(parents=True)
    exe.write_text("#!/bin/sh\n")
    exe.chmod(0o755)
    if link.is_symlink():
        link.unlink()
    link.symlink_to(exe)
    print(f"{label:30} -> {v._detect_install_method(argv0=str(link)).value}")

Expected Behavior

All four layouts should be detected as uv-tool or pipx. docs/upgrade.md:11 and docs/upgrade.md:39 say specify self upgrade "Auto-detects whether you installed via uv tool or pipx".

Actual Behavior

uv tool, default dir           -> uv-tool
uv tool, XDG_DATA_HOME=~/xdg   -> unsupported
pipx, legacy ~/.local/pipx     -> pipx
pipx, ~/.local/share/pipx      -> unsupported

Specify CLI Version

1.2.4.dev0 (main at 27d14e2)

AI Agent

Not applicable

Operating System

Linux 6.18 (Ubuntu container)

Python Version

Python 3.12.3

Additional Context

The tests in tests/specify_cli/test_version_detection.py:219-270 deliberately keep the registry fallback off for an existing entrypoint, so the gap is in the fixed prefix list rather than that gate. The uvx prefix ~/.cache/uv/archive-v0/ has the same limitation with XDG_CACHE_HOME. A likely fix is to derive the prefixes from uv tool dir and from PIPX_HOME and the pipx default rather than hard-coding them.

Also reproduced on macOS (Darwin 25.6, arm64) with Python 3.14.4 and uv run python -I repro.py against 27d14e2; the output matches the above.

AI Disclosure

Found and reproduced with Claude Code (model: Claude Opus 4.8), run autonomously under human supervision. The human reporter re-ran every reproduction above before filing.

Activity

  1. added
    triage-nice-to-haveVerdict: evidence-backed fix or greenlit feature — land after review
    on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage-nice-to-haveVerdict: evidence-backed fix or greenlit feature — land after review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions