[agent] Found by the scheduled Hatch bug-hunt routine (ledger #314).
Summary
Hatch 1.16 added workspaces ([tool.hatch.envs.<env>.workspace] members = [...]). A workspace root env installs every member editable and also installs each member's [project] dependencies. For members whose dependencies are static, Hatch takes the raw strings from the member's pyproject.toml (Workspace.get_dependencies → WorkspaceMember.get_dependencies → project.get_dependencies() in hatch/env/plugin/interface.py). No context formatting is applied.
A dependency declared only in a member is refused from the workspace root (redirect_hatch_unsupported, "transitive-only dependencies require agent mode"), so the natural next step is to scan from the member. There, scan --mode vendored rewrites the member's project.dependencies to six @ {root:uri}/.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl#sha256=… and reports success, applied: 1. From then on, the workspace root env can't sync:
ERROR: Could not install packages due to an OSError: No connection adapters were found for '{root:uri}/.socket/vendor/pypi/3c4d5e6f-…/six-1.16.0-py2.py3-none-any.whl'
This happens in the existing root env, because the dependency hash changed and Hatch re-syncs, and in every fresh root env, such as a CI checkout. The member's own env (cd libs/lib1 && hatch run …) is fine, because hatchling expands {root:uri} there.
socket-patch already refuses the same "Hatch won't expand {root:uri} here" situation for PEP 735 dependency groups (utils/hatch.rs:232). The member-consumed-by-a-workspace case has no such check.
Hosted mode on the same shape is fine: the absolute URL installs, and a fresh root env imports the patched six.
Impact
After a vendored scan in a workspace member, every hatch run / hatch shell / hatch test in the workspace root env fails, for every developer and in CI, until someone hand-edits the member's pyproject.toml or runs rollback. The scan reports success with no warning.
Repro (Linux, real Hatch 1.18.1 and 1.16.5, main 61cfb9b)
Uses a local mock of the patch API serving six 1.16.0 with a SOCKET_PATCHED marker (the same mock as #335 / #479), with SOCKET_PATCH_SERVER_URL pointed at it.
SP="socket-patch --api-url $API --api-token fake --org test-org"
mkdir -p ws/src/app ws/libs/lib1/src/lib1 && cd ws && touch src/app/__init__.py libs/lib1/src/lib1/__init__.py
cat > pyproject.toml <<'EOF'
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "app"
version = "0.1.0"
dependencies = []
[tool.hatch.build.targets.wheel]
packages = ["src/app"]
[tool.hatch.envs.default.workspace]
members = ["libs/*"]
EOF
cat > libs/lib1/pyproject.toml <<'EOF'
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lib1"
version = "0.1.0"
dependencies = ["six==1.16.0"]
[tool.hatch.build.targets.wheel]
packages = ["src/lib1"]
EOF
hatch env create
VIRTUAL_ENV=$(hatch env find default) $SP scan --mode vendored --json --yes --ecosystems pypi --cwd libs/lib1
# -> status success, vendor.summary.applied 1; libs/lib1/pyproject.toml now has "six @ {root:uri}/.socket/vendor/…"
hatch run python -c 'import six' # existing root env: pip OSError "No connection adapters were found for '{root:uri}/…'"
HATCH_DATA_DIR=$(mktemp -d) hatch run python -c 'import six' # fresh root env: same error
(cd libs/lib1 && HATCH_DATA_DIR=$(mktemp -d) hatch run python -c 'import six') # member env: works, patched
From the root, scan --mode vendored refuses as documented (member-only dependency). The same steps with --mode hosted pass.
Expected vs actual
- Expected: docs/testing/hatch.md says vendored references use
{root:uri}, and that a shape where Hatch doesn't expand it is refused before writing ("vendored groups are refused because Hatch does not expand {root:uri} within dependency groups"). Per CLI_CONTRACT.md, a refusal writes nothing and is reported. A member of a Hatch workspace whose static dependencies are consumed by a workspace env should be refused the same way, pointing at hosted or agent mode, or wired so the root can resolve it.
- Actual: the wiring is written, the scan reports
success with no warning, and the workspace root env is broken.
Matrix
| OS |
Hatch |
Mode |
Result |
| Linux |
1.18.1 |
vendored, from member |
fail (2×: existing and fresh root env) |
| Linux |
1.16.5 |
vendored, from member |
fail |
| Linux |
1.18.1 |
hosted, from member |
pass (fresh root env patched) |
| Linux |
1.18.1 |
vendored / hosted, from root |
refused as documented (member-only dep) |
| macOS / Windows |
— |
— |
untested. The cause is Hatch's raw read of member metadata, which doesn't depend on the OS. |
Hatch < 1.16 has no workspaces. First bad version: not bisected. The latest tag, v4.0.0, predates the Hatch lane (#244).
Suspect code
crates/socket-patch-core/src/utils/hatch.rs:206-210: project.dependencies is rewritten with the {root:uri} URL without checking whether the project is a member of an enclosing Hatch workspace. Compare the dependency-group refusal at utils/hatch.rs:232.
crates/socket-patch-core/src/vendor/pypi_hatch.rs:172 (wire): it only reads the cwd project's pyproject.toml / hatch.toml. It never looks at ancestor workspace.members.
[agent] Found by the scheduled Hatch bug-hunt routine (ledger #314).
Summary
Hatch 1.16 added workspaces (
[tool.hatch.envs.<env>.workspace] members = [...]). A workspace root env installs every member editable and also installs each member's[project] dependencies. For members whose dependencies are static, Hatch takes the raw strings from the member'spyproject.toml(Workspace.get_dependencies→WorkspaceMember.get_dependencies→project.get_dependencies()inhatch/env/plugin/interface.py). No context formatting is applied.A dependency declared only in a member is refused from the workspace root (
redirect_hatch_unsupported, "transitive-only dependencies require agent mode"), so the natural next step is to scan from the member. There,scan --mode vendoredrewrites the member'sproject.dependenciestosix @ {root:uri}/.socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl#sha256=…and reportssuccess,applied: 1. From then on, the workspace root env can't sync:This happens in the existing root env, because the dependency hash changed and Hatch re-syncs, and in every fresh root env, such as a CI checkout. The member's own env (
cd libs/lib1 && hatch run …) is fine, because hatchling expands{root:uri}there.socket-patch already refuses the same "Hatch won't expand
{root:uri}here" situation for PEP 735 dependency groups (utils/hatch.rs:232). The member-consumed-by-a-workspace case has no such check.Hosted mode on the same shape is fine: the absolute URL installs, and a fresh root env imports the patched six.
Impact
After a vendored scan in a workspace member, every
hatch run/hatch shell/hatch testin the workspace root env fails, for every developer and in CI, until someone hand-edits the member'spyproject.tomlor runs rollback. The scan reports success with no warning.Repro (Linux, real Hatch 1.18.1 and 1.16.5, main
61cfb9b)Uses a local mock of the patch API serving six 1.16.0 with a
SOCKET_PATCHEDmarker (the same mock as #335 / #479), withSOCKET_PATCH_SERVER_URLpointed at it.From the root,
scan --mode vendoredrefuses as documented (member-only dependency). The same steps with--mode hostedpass.Expected vs actual
{root:uri}, and that a shape where Hatch doesn't expand it is refused before writing ("vendored groups are refused because Hatch does not expand{root:uri}within dependency groups"). Per CLI_CONTRACT.md, a refusal writes nothing and is reported. A member of a Hatch workspace whose static dependencies are consumed by a workspace env should be refused the same way, pointing at hosted or agent mode, or wired so the root can resolve it.successwith no warning, and the workspace root env is broken.Matrix
Hatch < 1.16 has no workspaces. First bad version: not bisected. The latest tag, v4.0.0, predates the Hatch lane (#244).
Suspect code
crates/socket-patch-core/src/utils/hatch.rs:206-210:project.dependenciesis rewritten with the{root:uri}URL without checking whether the project is a member of an enclosing Hatch workspace. Compare the dependency-group refusal atutils/hatch.rs:232.crates/socket-patch-core/src/vendor/pypi_hatch.rs:172(wire): it only reads the cwd project'spyproject.toml/hatch.toml. It never looks at ancestorworkspace.members.