Repository navigation
Vendored dry run gives no symlink warning for uv script locks or pylock files: a symlinked s.py, s.py.lock, pylock.toml or pylock.<name>.toml previews clean, and the real run then refuses (exit 1) #891
Description
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
#802 added vendor_would_refuse_symlinked_file, so that vendor --dry-run and the scan / get --mode vendored --dry-run previews predict the wet run's symlink refusal. Its follow-up commits ("Warn about symlinked pom.xml and hatch.toml too") treat "the real run refuses with no dry-run hint" as a bug. The warning list comes from formats::registry::wiring_paths("pypi"), which holds only fixed file names (requirements.txt, uv.lock, pyproject.toml, poetry.lock, …). The PyPI lanes that write files with variable names are left out:
- the PEP 723 script lane:
<script>.pyand<script>.py.lock - the PEP 751 lane:
pylock.tomlandpylock.<name>.toml(uv writes these withuv export --format pylock.toml)
When one of those is a symlink, the dry run exits 0 with no warning. The wet run then refuses with pypi_lock_symlink_unsupported (exit 1, partial_failure). The refusal itself is correct and writes nothing. Only the preview is wrong.
Impact
A user or CI step that gates on the dry run sees a clean preview, and the real vendoring then fails. That's exactly the misprediction #802's dry-run warning was added to prevent. It shows up for the uv script and pylock layouts; the uv.lock / pyproject.toml lane is covered.
Repro (real uv, local mock patch server)
mkdir -p shared proj && cd proj && git init -q
cat > s.py <<'EOF'
# /// script
# requires-python = ">=3.9"
# dependencies = ["six==1.16.0", "attrs>=20"]
# ///
import six
EOF
uv lock -q --script s.py
mv s.py.lock ../shared/ && ln -s ../shared/s.py.lock s.py.lock # or link s.py, pylock.toml, pylock.dev.toml
socket-patch scan --mode vendored --dry-run --json # exit 0, no vendor_would_refuse_symlinked_file
socket-patch get pkg:pypi/six@1.16.0 --mode vendored --dry-run --yes --json # exit 0, no warning
socket-patch scan --mode vendored --yes --json # exit 1, partial_failure,
# pypi_lock_symlink_unsupported: s.py.lock is a symbolic link …Control: when uv.lock (or pyproject.toml) is linked instead, both dry runs emit vendor_would_refuse_symlinked_file, and the hosted --dry-run predicts its own refusal (exit 1) for every one of these files.
Expected vs actual
- Expected: the Fix vendored mode replacing symlinked lockfiles (#627) #802 contract (CLI_CONTRACT and the PR text: "A --dry-run flags each symlinked wiring file with a vendor_would_refuse_symlinked_file advisory"). docs/testing/uv-compatibility.md:132 lists
pylock*.toml,*.py.lockand script files among the symlinked files that every writer refuses. The dry run should warn for the same set. - Actual: no advisory for script / script-lock / pylock links, while the wet run refuses.
Matrix (main 9c43dfc, Linux)
| uv | symlinked file | scan dry run | get dry run | wet scan |
|---|---|---|---|---|
| 0.8.17 / 0.12.23 | uv.lock (control) |
warns | warns | refuses, exit 1 |
| 0.8.17 / 0.12.23 | s.py |
no warning, exit 0 | no warning | refuses, exit 1 |
| 0.8.17 / 0.12.23 | s.py.lock |
no warning, exit 0 | no warning | refuses, exit 1 |
| 0.12.23 | pylock.toml |
no warning | no warning | refuses, exit 1 |
| 0.12.23 | pylock.dev.toml |
no warning | no warning | refuses, exit 1 |
macOS / Windows probe (0.8.17 / 0.12.23): https://github.com/SocketDev/socket-patch/actions/runs/37370603015 (queued; I'll add the results as a comment). The list is a fixed set of file names, so I expect every OS to behave the same. First bad: #802 (6b8c076) introduced the warning with this gap. Before it, the wet run replaced the link (#627).
Suspect code
crates/socket-patch-core/src/formats/registry.rs:246:wiring_pathsreturns static registry rows only. There are no rows or patterns for*.py.lock, their scripts, orpylock*.toml.crates/socket-patch-cli/src/commands/vendor.rs:401:symlinked_wiring_warningschecks only that list. It could reusepython_lock_paths/script_of_lock(aspypi_lock.rsdoes for the wet refusal) to cover the files the PyPI lock lane would really write.
Backlog review — 2026-10-08
Priority: P1 → P3. Real writes refuse the symlink safely; only the dry-run preview misses the warning.
Activity
mikolalysenko commentedon Oct 5, 2026
[agent] The probe run (https://github.com/SocketDev/socket-patch/actions/runs/37370603015) confirms this on Windows (native symlinks, uv 0.12.23) and on ubuntu-latest (uv 0.12.23). The output is identical on both:
uv.lockgetswarn=1, whiles.py,s.py.lock,pylock.tomlandpylock.dev.tomleach getwarn=0on the scan and get dry runs, and the wet run fails withpypi_lock_symlink_unsupported(exit 1, link and target untouched). The macOS jobs were still queued when this run ended.Generated by Claude Code
uv bug-hunt run 21: #890, #891; #723 / #767 still fail
mikolalysenko commentedon Oct 5, 2026
[agent] Triaged as
priority:p1(uv). Confirmed onmain:symlinked_wiring_warnings(crates/socket-patch-cli/src/commands/vendor.rs:400) checks onlyformats::registry::wiring_paths("pypi")(crates/socket-patch-core/src/formats/registry.rs:246), and that list holds only fixed file names. The variable-name PEP 723 (<script>.py,<script>.py.lock) and PEP 751 (pylock.toml,pylock.<name>.toml) wiring files are never probed. A fix needs the dry run to enumerate the variable-name wiring files the wet run would discover. No duplicate or open PR found.Generated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] macOS evidence from the run-21 probe, which finished after the issue was filed. On main
9c43dfc, macos-latest × uv 0.8.17 and 0.12.23 behave the same as Linux and Windows:0.8.17 gives the same results (job 111966486095). Job: https://github.com/SocketDev/socket-patch/actions/runs/37370603015/job/111966486055
That makes the OS table Linux ✗, Windows ✗, macOS ✗.
Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 triage: P3, not a release blocker. Dry-run parity for symlinked variable-name lockfiles is a narrow refusal diagnostic. The real write already refuses; P3.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 13, 2026
[agent] New data from the uv bug-hunt run on main
15c2c83(uv 0.13.0, Linux). The symlink case is one symptom of a wider gap:scan/get --mode vendored --dry-rundon't run the uv wiring preflight (check_target_guards) at all, but standalonevendor --dry-rundoes.scan/get --mode vendored --dry-runscan --mode vendoredvendor --dry-run[tool.uv] no-sources = true(new #564 refusal, #1434), project laneverifiedpypi_uv_no_sourcesfailedpypi_uv_no_sourcesuv.toml, orno-sources-package = ["six"]verifiedpypi_uv_no_sourcess.py.lock)verifiedpypi_uv_no_sources[tool] uv = { sources = … }(#979's shape)verifiedpypi_uv_lock_parse_failedAll 6 no-sources cells preview exactly what a clean project previews. The contract lists only the Bun and pnpm-takeover refusals as previewed by the scan dry run, so I'm adding this here instead of opening a new issue. A fix that runs the pypi preflight in the scan/get dry run would cover the symlink, no-sources and inline-sources shapes together.
Generated by Claude Code