Skip to content

remove and rollback don't PEP 503-normalise PyPI purl identifiers, so remove pkg:pypi/typing_extensions@4.7.1 exits 1 "No patch found" while get accepts the same identifier #1024

Description

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

Summary

#910 / #911 and #926 / #927 taught scan --package, socket.yml and get to compare PyPI names by their PEP 503 canonical form. remove and rollback were left out. Both match identifiers through patch_matches → purl_matches_identifier, which compares the purl text exactly. Every patch key on disk is canonical (pkg:pypi/typing-extensions@4.7.1), so an identifier spelled the way the project declares the package fails with No patch found matching identifier, exit 1, and nothing is unwound. That covers typing_extensions as written in pyproject.toml, Jinja2, Typing-Extensions and ruamel.yaml. This happens in all three modes (agent, hosted, vendored).

So get pkg:pypi/typing_extensions@4.7.1 patches the package, but remove pkg:pypi/typing_extensions@4.7.1 (the same string) can't undo it.

Impact

  • A user, or a script, that removes or rolls back the patch it just applied by the same identifier gets exit 1 and keeps the patch: the lock stays hosted or vendored, and the agent-mode files stay patched.
  • Poetry users meet this naturally. Poetry keeps the spelling from pyproject.toml (typing_extensions = "…", Jinja2 = "…"), and the purl spec's PyPI rule (lowercase, _ → -) isn't something users apply by hand.
  • It isn't Poetry-specific. The matcher is ecosystem-generic, so pip, uv, Pipenv, PDM and Hatch projects behave the same way. It's filed from the Poetry lane, where it was found.

Repro (Linux, Poetry 2.5.1, a local mock patch API with free patches for six@1.16.0 and typing-extensions@4.7.1)

mkdir app && cd app
cat > pyproject.toml <<'EOF'
[tool.poetry]
name = "demo-two"
version = "0.1.0"
description = ""
authors = ["x <x@example.com>"]
package-mode = false

[tool.poetry.dependencies]
python = "^3.9"
six = "1.16.0"
typing_extensions = "4.7.1"
EOF
poetry lock
socket-patch scan --mode hosted --ecosystems pypi --yes ...      # both pinned to hosted wheels
socket-patch remove pkg:pypi/typing_extensions@4.7.1 --yes ...   # exit 1
# Error: No patch found matching identifier: pkg:pypi/typing_extensions@4.7.1
socket-patch remove pkg:pypi/typing-extensions@4.7.1 --yes ...   # exit 0, restored (control)

Expected vs actual

Cells (main db83f01, Linux, Poetry 2.5.1; each cell run in a fresh copy)

Mode Command pkg:pypi/typing-extensions@4.7.1 (control) pkg:pypi/typing_extensions@4.7.1 pkg:pypi/Typing-Extensions@4.7.1
agent remove ✅ exit 0, file restored ❌ exit 1, still patched ❌ exit 1, still patched
agent rollback ✅ exit 0 ❌ exit 1 ❌ exit 1
hosted remove ✅ exit 0, lock entry restored ❌ exit 1, still hosted ❌ exit 1
hosted rollback ✅ exit 0 ❌ exit 1 ❌ exit 1
vendored remove ✅ exit 0, vendoring reverted ❌ exit 1, still vendored ❌ exit 1
vendored rollback ✅ exit 0 ❌ exit 1 ❌ exit 1
hosted get pkg:pypi/typing_extensions@4.7.1 — ✅ pinned (asymmetry) —

The underscore case reproduced 3 times for hosted remove (two separate harness runs). OS doesn't matter: this is string matching. Not bisected.

Suspect code

  • crates/socket-patch-core/src/utils/purl.rs:421 patch_matches → purl_matches_identifier: no canonicalize_pypi_name on the pkg:pypi/ name before comparing. pypi_purl (same file) already canonicalizes the keys it builds.
  • Callers: crates/socket-patch-cli/src/commands/remove.rs (the manifest, vendor ledger and hosted-pin filters) and crates/socket-patch-cli/src/commands/rollback.rs (RollbackTarget::Identifier).
  • Move canonicalize_pypi_name and the PEP 508 name scanner out of crawlers and vendor into one PyPI name module #883 (one PyPI name module) is the natural home for the shared normaliser.

Activity

  1. added a commit that references this issue on Oct 7, 2026
  2. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: remove/rollback identifier matching compares PyPI purl names without PEP 503 canonicalization). Branch: agent/fix-pypi-identifier-canonical-match. Claim-ID: 2026-10-07T14:21:35Z-a2b9e1


    Generated by Claude Code

  3. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1025


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions