Skip to content

Fix silent PyPI hosted scan with no lockfile (#638) - #1885

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-pypi-hosted-no-lockfile-warning
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-pypi-hosted-no-lockfile-warning

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #638

Summary

A scan --mode hosted --json over a pip project whose venv holds a
patched package, but which has no root requirements.txt or Python lock,
exited 0 with an empty warnings list. The only machine-readable signal
was the per-purl redirect_unconfirmed row (#1029). Every other
ecosystem reports this case with a stable code
(redirect_npm_no_lockfile, redirect_composer_no_lockfile,
redirect_gem_no_gemfile, ...). PyPI now reports
redirect_pypi_no_lockfile, once per run.

Layouts covered (the issue's matrix and its uv comment):

  • only requirements-dev.txt
  • only requirements/base.txt
  • no requirements file at all
  • a pyproject.toml project whose uv.lock is not committed (the
    detail tells the user to run uv lock / poetry lock / pdm lock)

Root cause

Each PyPI hosted rewriter (pdm, Pipenv, requirements, Hatch, uv/pylock,
Poetry) acts only on the file it owns and returns silently when that file
is absent. No PyPI-level check covered "none of them is present", unlike
the npm branch in patch/redirect/mod.rs or composer's
formats/composer/hosted.rs.

Fix

warn_no_pypi_wiring runs last in the PyPI rewriter group
(patch/redirect/mod.rs). When a pypi patch reaches the lane and the
project has no root requirements.txt, uv.lock / poetry.lock /
pdm.lock / Pipfile.lock, PEP 751 / PEP 723 lock, or Hatch
configuration, it pushes one warning. The warning names the files looked
for and says that other requirements files (such as requirements-dev.txt)
are not read. When any of those files is present, it owns the diagnostics
(e.g. redirect_requirements_entry_not_found) and no warning is added.
Patches withheld earlier (platform wheels #701, pdm/Pipenv refusals) don't
trigger it. Unreadable or undecodable candidate files already refuse the
project before the rewriters run, so they can't produce a misleading
"no lockfile".

CLI_CONTRACT.md lists the new code as an additive v5.0 redirect_*
warning. Wrappers (npm/, pypi/, gem/) don't name warning codes,
so they need no change.

Out of scope, as the issue says: the hosted exit status for an
unpinned patch is still 0 (the open #704 decision).

Tests

Issue item Test
#638: requirements-dev.txt / requirements/base.txt / no file / pyproject-only each warn once patch::redirect::pypi_no_lockfile_tests::every_lockless_layout_warns_once
#638 (uv comment): pyproject-only detail names the lock step pypi_no_lockfile_tests::pyproject_without_a_lock_names_the_lock_step
no false positive beside any PyPI wiring file or a Hatch config pypi_no_lockfile_tests::a_python_lock_or_root_requirements_suppresses_the_warning
no warning without a pypi patch in the lane (incl. withheld platform wheel) pypi_no_lockfile_tests::no_warning_without_a_pypi_patch_in_the_lane
end to end: --json envelope carries the code once, keeps the redirect_unconfirmed row, writes nothing, exit 0 mode_migration_pypi::hosted_scan_without_a_python_lock_warns_no_lockfile

Red → green:

  • Before the fix: cargo test -p socket-patch-core --lib pypi_no_lockfile
    gave 2 failed (every_lockless_layout_warns_once: "requirements-dev.txt
    only: []", pyproject_without_a_lock_names_the_lock_step), 2 passed
    (the negative cases).
  • After the fix: 4 passed.

Local runs:

  • cargo fmt --all -- --check: ok
  • cargo clippy --workspace --all-features -- -D warnings: ok
  • cargo test -p socket-patch-cli --all-features --test mode_migration_pypi:
    75 passed (the new test re-run on d0dc53c: passed)
  • pypi_no_lockfile_tests on d0dc53c: 6 passed
  • cargo test -p socket-patch-core --all-features --lib: 6998 passed and
    4 failed. All 4 failures depend on chmod making a directory unwritable
    (copy_tree::relax_loop_must_not_traverse_symlinked_root,
    vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,
    pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,
    pypi_requirements::wire_failure_rolls_back_already_written_files), so
    they can't pass in this container, which runs as root (uid 0). None of
    them is touched by this diff.
  • A full cargo test --workspace --all-features ran out of disk in this
    session's container (about 20G of build cache). CI runs the whole
    workspace.

/code-review high

Fixed in d0dc53c:

  • The remedy names the project's own lock step: pipenv lock for a
    Pipfile, and uv lock / poetry lock / pdm lock when pyproject
    names that tool (a [tool.*] table or its build backend). A plain
    pyproject gets both a root requirements.txt pin and the lock commands.
    Tested by the_remedy_names_the_projects_own_lock_step.
  • The detail lists every silencing input (pylock..toml, PEP 723
    script lock, Hatch) and says "readable", because on disk an unreadable
    file is read as absent (the_detail_names_every_input_looked_for).
  • The presence check matches root keys only, like the rewriters it
    stands in for.

Not changed:

  • The presence list could drift from the rewriters' inputs. It reuses
    the shared governing_locks / python_lock / hatch predicates the
    rewriters themselves use. Having each rewriter report "saw an input"
    would touch six rewriters for a single warning. The suppression test
    covers every input, so a new input that isn't added here fails there
    only if a test for it is added; flagged here for review.
  • A second is_hatch TOML parse. It runs only on the lockless path,
    which is once per run and has nothing else to do, so it isn't worth
    threading state between rewriters.

Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A hosted scan of a Python project with no root requirements.txt, tool
lock (uv.lock, poetry.lock, pdm.lock, Pipfile.lock), PEP 751 or PEP 723
lock, or Hatch configuration left its PyPI patches unpinned while
`--json` showed an empty redirect.warnings list. Every other ecosystem
reports this case (redirect_npm_no_lockfile and friends).

The PyPI rewriter lane now adds one redirect_pypi_no_lockfile warning
per run in that case. It names the files looked for, notes that other
requirements files such as requirements-dev.txt are not read, and tells
a pyproject.toml project to generate and commit its lock.

Refs #638

Assisted-by: Claude Code:claude-opus-5-5
Adds a hosted scan test for each project layout in #638 that has no
Python lock: only requirements-dev.txt, only requirements/base.txt, no
requirements file, and a pyproject.toml without uv.lock. Each run must
report the warning once, keep the redirect_unconfirmed row, and write
nothing. CLI_CONTRACT.md now lists the new warning code.

Refs #638

Assisted-by: Claude Code:claude-opus-5-5
Review follow-up for redirect_pypi_no_lockfile. The remedy now follows
the project's tooling: a Pipfile is told to run `pipenv lock`, and a
pyproject that names uv, Poetry or PDM (a [tool.*] table or its build
backend) is told to run that tool's lock. Any other pyproject, such as a
plain setuptools project, gets both a root requirements.txt pin and the
lock commands. The detail also lists every input that would silence it
(pylock.<name>.toml, PEP 723 script locks, Hatch) and says "readable",
since an unreadable file on disk is treated as missing. The presence
check now matches root keys only, like the rewriters it stands in for.

Refs #638

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 30, 2026 06:18
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d0dc53c. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 30, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at d0dc53c (burn-down agent).

  • CI: 82/82 checks green on the head (32 success, 50 path-skipped), no failures.
  • Bugbot: reviewed d0dc53c, no new issues; no unresolved review threads.
  • Mergeable, no conflicts; no CHANGELOG.md change.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (d0dc53c)

What it does. Before this PR, a scan --mode hosted over a pip project with a patched package returned no warnings at all when the project root had no requirements.txt, Python lock or Hatch config (#638). This PR adds warn_no_pypi_wiring as the last step of the PyPI rewriter group. When a pypi patch reaches that step and no PyPI input file exists, it adds one redirect_pypi_no_lockfile warning per run. The warning lists the files it looked for and suggests a fix for the project's own tool (uv lock, poetry lock, pdm lock, pipenv lock, or a requirements.txt pin).

Risk: low. It only adds a warning on a path that used to say nothing. It writes no files, and the exit code and existing rows are unchanged. It reuses the same file checks and constants the rewriters use, so it cannot fire next to a confirmed pin.

Look here

  • call site, last in the PyPI group
  • warn_no_pypi_wiring:`` when it fires and the presence check
  • pypi_no_lockfile_remedy
  • the suppression tests````
  • end-to-end --json test

Verified

  • Read the full diff and PyPI hosted scan lacks a specific missing-requirements warning after redirect_unconfirmed reporting #638.
  • Ran cargo test -p socket-patch-core --lib pypi_no_lockfile: 6 passed.
  • Ran cargo test -p socket-patch-cli --test mode_migration_pypi: 75 passed.
  • Ran the full core --lib: 7000 passed. 4 root-only chmod tests failed; they fail the same way on main and none is in changed code.
  • Turned off only the new call. 4 of the 6 new core tests and the CLI test then fail, so the tests catch it.
  • cargo clippy -p socket-patch-core -p socket-patch-cli -- -D warnings is clean.
  • CI on d0dc53c: 82 check runs, 32 passed and 50 skipped. Bugbot found no issues. No open review threads. CHANGELOG.md is untouched.

Changes I made: none.

Open questions (optional nits)

  • CLI_CONTRACT.md lists the new code only inline, with no row in the per-code table. redirect_npm_no_lockfile and redirect_composer_no_lockfile have no rows either, so this matches existing practice.
  • A pyproject with both [tool.uv] and [tool.poetry] is told to run uv lock, because uv is checked first.

Auto-merge is armed, so approving sends this straight to the merge queue.


Generated by Claude Code

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

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PyPI hosted scan lacks a specific missing-requirements warning after redirect_unconfirmed reporting

2 participants