Repository navigation
Scan from a hatchling member of a uv workspace rewrites the member's pyproject.toml as a lockless Hatch project, so the root uv.lock goes stale, uv sync --frozen installs the unpatched release and vendored vex attests not_affected #1138
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:uvuvuv
on Oct 8, 2026 - added a commit that references this issue
on Oct 8, 2026 mikolalysenko commented
on Oct 8, 2026 CollaboratorAuthorMore actions[agent] Triaged: priority:p1 (uv). No duplicate or open PR. The governing-root pre-check (
hosted/governing_root.rs) has no uv-workspace case for a member whose ownpyproject.tomlhas nouv.lockbeside it.detect_pypi_flavorthen falls through to the hatchling → hatch branch. This is the uv counterpart of #884, #590, #942 and #417. Related, but not the same fix: #505 (Hatch's own workspaces).
Generated by Claude Code
mikolalysenko commented
on Oct 8, 2026 CollaboratorAuthorMore actions[agent] More from the uv bug-hunt routine (ledger #310), on main
4657813, Linux.The trigger is any Hatch config in the member, not only the hatchling build backend. A fix keyed only on step 8 of
detect_pypi_flavor(the hatchling backend) would miss these. Every member below sits in a uv workspace (members = ["packages/*"], rootuv.lock), and the scan runs frompackages/a:member packages/auv vendored scan hosted scan uv sync --frozenfrom rootvendored vexno build-system + hatch.toml([envs.default])0.5.31, 0.12.23 exit 0, member rewritten ( six @ {root:uri}/…,[tool.hatch.metadata])exit 0, member rewritten (hosted URL) unpatched not_affected no build-system + [tool.hatch.envs.default]in pyproject0.12.23 same same unpatched – build-backend = "uv_build"+hatch.toml0.12.23 same same, and it adds [tool.hatch.metadata] allow-direct-references = trueto a uv_build projectunpatched not_affected Control (already in the issue): the same member with no
hatch.tomland no[tool.hatch]table givespypi_pyproject_only(vendored), and hosted writes nothing.So the governing-root check needs to run before any Hatch detection (hatchling backend,
hatch.tomlor a[tool.hatch]table), not only before the hatchling branch.Also checked: a non-member nested project inside the workspace (
tools/x, outside themembersglob, with its ownuv.lock) is wired normally in both modes, and--lockedinstalls the patched wheel. A fix shouldn't refuse that shape.
Generated by Claude Code
- addedv5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.Must resolve before v5: public interface/migration or ordinary patch-install-undo failure.compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.Public CLI/JSON, saved state, upgrades, or package-manager compatibility.
on Oct 9, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 release blocker (P1). A standard uv workspace member using hatchling must resolve to its governing uv.lock, not be rewritten as an unrelated lockless Hatch project.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Claiming for v5 blocker burn-down (shared root cause: PyPI flavor detection ignores an ancestor uv workspace / uv.lock that governs the member). Branch: agent/v5-uv-workspace-member. Claim-ID: 20261009T164150Z-9798c2
- added 2 commits that reference this issue
on Oct 9, 2026
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
In a uv workspace,
uv.locklives at the workspace root and governs every member. Whensocket-patch scanruns with--cwd(or the shell) in a member directory whosepyproject.tomluses the hatchling build backend, the run never sees the ancestoruv.lock.detect_pypi_flavorfalls through to step 8, "hatchling build backend → hatch", and both modes rewrite the member as a lockless Hatch project:six @ {root:uri}/.socket/vendor/pypi/<uuid>/six-1.16.0-…whl#sha256=…into the member'sdependencies, puts the wheel underpackages/a/.socket/, and adds[tool.hatch.metadata] allow-direct-references = true. It exits 0success.six @ http://<patch-server>/…/six-1.16.0-…whl#sha256=…into the same place and exits 0success.The root
uv.lockis never touched, so it no longer matches the workspace:uv lock --checkanduv sync --lockedfail at the root.uv sync --frozen, which is what CI and Docker builds use, installs the unpatched six from PyPI.vex, from the member, attestsnot_affected, although the frozen install has unpatched bytes.uv syncrelocks, and vendored mode then writes an absolute, machine-specific path (source = { path = "/home/<user>/…/packages/a/.socket/vendor/…whl" }) into the committeduv.lock. That lock breaks on every other checkout.This is a realistic layout.
uv init --packagescaffolds hatchling members on uv < 0.8 (verified on 0.5.31; 0.12.23 scaffoldsuv_build), so every workspace created with those uv versions has hatchling members. It is the uv-workspace counterpart of the governing-root refusals for npm / yarn / Bun (#884), pnpm (#590), vlt (#942) and cargo (#417), and the uv-side sibling of #505 (Hatch's own workspaces).Repro (Linux, uv 0.12.23 or 0.5.31)
I served patch data from a local mock of the patch API (
six@1.16.0, which appends a marker tosix.py), the same mock as earlier uv issues.Expected vs actual
--cwdis a workspace member … reads the member's directory only, while the package manager installs from a lock in an ancestor directory",hosted/governing_root.rsmodule doc). Running from the workspace root is already refused, by design, withpypi_uv_workspace_unsupported/redirect_uv_project_unsupported. The member path should not quietly succeed where the root path refuses. A wired package also must not be attestednot_affectedwhile a frozen install of the project's lock installs the unpatched release.successin both modes, a stale rootuv.lock, unpatched frozen installs, and anot_affectedVEX (vendored).Matrix (Linux; real uv lock / sync)
uv lock --checkuv sync --frozen.venv)pypi_pyproject_only/ exit 0 with nothing writtenpypi_pyproject_only/ exit 0 with nothing written[build-system]pypi_pyproject_only/ exit 0 with nothing writtenrollbackfrom the member restores the member'spyproject.tomlbyte-identically in both modes, so the damage is reversible once someone notices it.Not bisected. The hatch routing predates the last release, and the ledger's Sept 30 note ("scan from a member dir falls through, 0 patches") was taken with a member that had no hatchling backend. No probe branch: this is path logic with no OS-specific branch.
Suspect code
crates/socket-patch-core/src/vendor/pypi.rs:269(detect_pypi_flavor) only looks atproject_root. Its step 8 (return Ok((PypiFlavor::Hatch, …)), line 428) claims a hatchling member without checking for an ancestorpyproject.tomlwhose[tool.uv.workspace]members cover this directory, or an ancestoruv.lock.crates/socket-patch-core/src/hosted/governing_root.rs:72(refusal) has cargo and npm-family arms but no PyPI / uv-workspace arm, so the hosted Hatch rewrite isn't pre-empted either.