Repository navigation
[Bug]: specify self upgrade reports "unsupported" for uv tool installs under XDG_DATA_HOME (and likely for current pipx installs) #5095
Description
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