Skip to content

Hatch never picks up a superseding patch: re-scan refuses its own earlier wiring ("existing direct source must be reverted"), so hosted exits 0 still pinned to the old patch uuid #650

Description

[agent] Found by the scheduled Hatch bug-hunt routine (ledger #314).

Summary

When the patch API publishes a new patch uuid for a package that is already wired in a Hatch project (a superseding or fixed patch), socket-patch scan reports the update (updates: [{oldUuid, newUuid}]) but never re-wires pyproject.toml / hatch.toml. The Hatch planner treats socket-patch's own earlier direct reference as an unknown user source and refuses it:

  • hosted: status: success, exit 0, rewrittenFiles: [], plus only a warning redirect_hatch_unsupported: "six: an existing direct source must be reverted before patching". Both declarations still point at the old uuid's URL.
  • vendored: exit 1 partial_failure with pypi_hatch_unsupported and the same message. .socket/vendor/pypi/<old uuid>/ stays wired.

The same flow on a plain requirements.txt project re-wires to the new uuid (verified below), so this is specific to the Hatch lane.

Impact

  • A superseding patch never reaches Hatch users. Hosted CI shows green (exit 0, success), and the only signal is a warning that tells the user to "revert" a source they never wrote.
  • If the old artifact is withdrawn (for example, a patch was replaced because it was broken), every fresh hatch run fails: pip gets a 404 on the old uuid URL while scan keeps reporting success.
  • Workaround: socket-patch rollback, then scan again. Verified in both modes, so the user has to know to do that.

Repro (Linux, Hatch 1.18.1, local mock patch API)

The mock serves six 1.16.0 under uuid A. It is then restarted to serve the same package under uuid B (a different patched six.py). Mock and driver are the same shape as in the earlier probe runs.

mkdir -p app/src/app && touch app/src/app/__init__.py && cd app
cat > pyproject.toml <<'T'
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "app"
version = "0.1.0"
dependencies = ["six==1.16.0"]
[tool.hatch.envs.default]
dependencies = ["six==1.16.0"]
T
SPA="--api-url $API --api-token fake --org test-org --patch-server-url $API --ecosystems pypi"
socket-patch scan --mode hosted --yes --json $SPA      # mock serves uuid A -> both decls wired to .../A/six-1.16.0-...whl#sha256=...
# patch API now serves superseding uuid B for pkg:pypi/six@1.16.0
socket-patch scan --mode hosted --yes --json $SPA
#  exit 0, status success, updates [{oldUuid: A, newUuid: B}]
#  redirect: rewrittenFiles [], warnings [redirect_hatch_unsupported "six: an existing direct source must be reverted before patching"]
grep -c A pyproject.toml   # 2 -- still the old uuid
HATCH_DATA_DIR=$PWD/fresh hatch run python -c 'import six'
#  ERROR: Could not install requirement six @ http://…/A/six-1.16.0-py2.py3-none-any.whl … 404   (old artifact gone)

With --mode vendored, the second scan exits 1 (pypi_hatch_unsupported, same message), and a fresh env still imports the uuid-A bytes (six.SOCKET_PATCHED == 1, not 2).

Control: the same two-step flow with only requirements.txt (six==1.16.0) rewrites to …/B/… with rewrittenFiles: ["requirements.txt"].

Expected vs actual

  • Expected: a newer patch for an already-wired package is re-pinned on the next scan, as the requirements.txt lane does. docs/testing/hatch.md says "Unknown direct sources require an explicit revert before patching" and "Repeated vendored scans compare the declared source with the committed artifact path". The wiring socket-patch wrote itself isn't an unknown source.
  • Actual: any existing direct reference whose URL isn't byte-identical to the new one is refused, including socket-patch's own previous hosted URL and its own {root:uri}/.socket/vendor/pypi/<uuid>/… path. Hosted reports success.

Matrix (Linux, main 045d7ec)

Hatch hosted vendored
1.16.5 fail (exit 0, old uuid kept) fail (exit 1)
1.18.1 fail (exit 0, old uuid kept), reproduced 2× fail (exit 1), reproduced 2×
control: requirements.txt (no Hatch) re-wired to new uuid n/a

macOS and Windows weren't probed: the routine can't delete probe branches in this sandbox. The code path is OS-independent string comparison. No published release includes the Hatch lane (latest tag v4.0.0), so there's nothing to bisect.

Suspect code

  • crates/socket-patch-core/src/utils/hatch.rs:141-148 (replacement): if existing == url { … } else Err("an existing direct source must be reverted before patching"). It doesn't recognise a previous socket-patch hosted URL (same package and version, different uuid) or a previous vendored {root:uri}/.socket/vendor/pypi/<uuid>/ path as replaceable.
  • crates/socket-patch-core/src/patch/redirect/mod.rs:787-790: the hosted lane turns that error into a warning only, so scan still reports success.
  • crates/socket-patch-core/src/vendor/pypi_hatch.rs:102 and :181: the vendored callers.

Related, other PM: #266 (hosted Maven never re-pins on a superseding uuid).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions