GeezerVison is showing github.com — Scope project-mode NuGet crawls to the restore's resolved packages (#427) by mikolalysenko · Pull Request #1183 · SocketDev/socket-patch
Conversation
LLM Description written by Claude Code:claude-opus-5-5
Fixes #427
Summary
A project-mode scan of a restored .NET project used to list every package in the shared NuGet packages folder (~/.nuget/packages, NUGET_PACKAGES, every packageFolders key). Agent mode then patched packages that other projects had left there, and VEX attested them. This PR turns the crawler into a locator for restored projects. When cwd holds a .csproj/.fsproj/.vbproj, and every project under cwd at any depth has a parsed obj/project.assets.json with libraries, the shared roots are no longer walked. They are looked up only for the union of those resolved libraries (type: "package").
Why (leverage)
What changed (crawlers/nuget_crawler.rs only)
package_roots builds the root list, plus an optional scope. get_nuget_package_paths (same output as before) and crawl_all both use it.
is_dotnet_project became dotnet_root, which returns None, Marker (solution, packages.config or NuGet config) or Project. Only Project roots are scoped.
parse_project_assets parses one assets file into packageFolders and libraries. It replaces parse_project_assets_package_folders.
restore_scope walks the source tree and unions every project directory's libraries. It returns None if any project is uncovered.
locate_libraries probes <root>/<id lowercased>/<version lowercased>/ with the same verify_nuget_package and path_safety::is_safe_name_version guards as find_by_purls. It emits the same rows the walk produced for those directories.
Deleted
- The whole-cache walk of the shared roots for a fully restored project.
Behavior
- Changed: a fully restored project no longer crawls packages from the shared roots that its restores did not resolve. "Fully restored" means a project file at
cwd, and every project file under it has a parsed assets file with libraries. The resolved packages come out with the same name, version, purl and path as before, but in libraries order instead of readdir order.
- Unchanged:
--global / --global-prefix;
- solution roots without a root project file;
- any tree where some project at any depth has no assets file or an assets file without
libraries;
- any tree with a directory that can't be fully listed, or more than 10,000 directories;
- the project-local
packages/ folder, which is still walked in full;
get_nuget_package_paths and find_by_purls.
- Remaining for Tracking: scope project-mode cache crawls to what the project resolves #595's NuGet item: the
packages.lock.json fallback, and the crawl_unscoped_cache warning. The warning needs CLI_CONTRACT.md, which open PRs change.
Test evidence
-
New unit tests in nuget_crawler.rs:
restored_project_crawls_only_its_resolved_packages
located_rows_equal_the_walks_rows_for_resolved_packages
sub_project_restores_join_the_scope (a restored sub-project two levels down)
an_uncovered_sub_project_keeps_the_walk (review fix: an unrestored sub-project two levels down, and an assets file without libraries)
project_local_packages_folder_is_still_walked
solution_roots_and_unrestored_projects_keep_the_walk
global_prefix_crawl_is_not_scoped
-
Red → green:
-
cargo clippy --workspace --all-features -- -D warnings is clean.
-
cargo test -p socket-patch-core --lib: 5805 passed on a05d7fc. Its only 4 failures are the known root-only sandbox ones, which also fail on main:
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
On 038042f I re-ran only the 40 crawlers::nuget tests, and they pass.
-
crawler_nuget_e2e: 28 passed. CLI e2e_nuget: 21 passed. The NuGet crawler oracle equivalence suite also passes: its trees have no libraries, so they keep the walk.
-
Timing (release build; I used a throwaway bench and removed it before committing). The fixture is a 3,000-package shared folder and a project that resolves 20 of them:
main: 58.5 ms per crawl, 3,000 packages;
- branch: 0.80 ms per crawl, 20 packages.
-
No dotnet in the sandbox, so I didn't run a real restore. The fixtures follow the assets format: libraries keyed "<Id>/<Version>", with type: package|project.
Risk
Medium.
- The scope walk lists the source tree. It skips hidden, symlinked,
bin, obj, packages and node_modules dirs, and gives up after 10,000 directories.
- Any project the walk finds without a parsed restore, including one with a relocated
BaseIntermediateOutputPath, makes the crawl fall back to the whole-cache walk. So a package is dropped only when no project's restore resolves it.
- A project outside
cwd that is referenced by path is covered too, because its packages appear in the referencing project's libraries.
- The wrappers (
npm/, pypi/, gem/) need no change.
🤖 Generated with Claude Code
Generated by Claude Code
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Assisted-by: Claude Code:claude-opus-5-5
A project-mode scan of a restored .NET project listed every package
in ~/.nuget/packages, so agent mode patched, and VEX attested,
packages other projects left in the shared cache (#427).
When the working directory is a project (.csproj/.fsproj/.vbproj)
whose obj/project.assets.json parses, the shared roots (the global
packages folder and packageFolders) are now looked up for the
restore's resolved libraries instead of walked. Solution roots,
unrestored projects, global mode and the project-local packages/
folder keep the walk. Each assets file is parsed once for both its
package folders and its libraries.
A 3,000-package cache crawl drops from 58 ms to 0.8 ms.
Assisted-by: Claude Code:claude-opus-5-5
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
left a comment
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The scope only read the root project's restore and the one-level-deep
ones, so a sub-project deeper down (or one whose assets file has no
libraries) lost its packages from the crawl without a warning.
The scope now walks the source tree (skipping hidden, symlinked, bin,
obj, packages and node_modules dirs, up to 10,000 directories) and
unions the libraries of every project directory's restore. Any project
without a parsed restore, an unreadable directory or an exhausted
budget keeps the whole-cache walk, as on main.
Assisted-by: Claude Code:claude-opus-5-5
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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 038042f. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
[agent] test (windows-latest, 1) failed on 038042f in vendor::lock_inventory::view::tests::a_read_set_notices_a_changed_file (view.rs:1182, assertion "a replacement"). This PR doesn't touch that code:
Likely root cause (Windows only). On non-Unix, Stat::of uses the file's creation time as its identity. The test writes a.tmp and renames it over a.lock, with the same length and the same bytes. NTFS file-system tunnelling keeps the old creation time when a file is renamed over a recently deleted name. modified can also tie at timer resolution. When both happen, the fingerprint can't see the replacement, so the result depends on timing.
Proposed fix (outside this PR's scope). On non-Unix, record every read as racy, so unchanged() compares the bytes it read against the file's current bytes. That is the existing racy path. An alternative is the Windows volume serial plus file index, once windows_by_handle is stable. No fix PR exists yet.
I'm re-running the failed job once to confirm.
Generated by Claude Code
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
[agent] The one re-run of test (windows-latest, 1) failed again on 038042f, but in a different test. lock_inventory::view::tests::a_read_set_notices_a_changed_file passed this time. The failure is now patch::apply_lock::tests::zero_timeout_does_not_sleep_before_held (apply_lock.rs:1106): "non-blocking acquire should not sleep, took 116.2534ms".
This one isn't this PR's either: that test checks wall-clock time for apply-lock acquisition, which this PR doesn't touch, and it passes on main and on this PR's earlier head a05d7fc. So the job has now failed two different timing-sensitive Windows tests in a row, both outside this diff.
Proposed fix (outside this PR's scope). Assert that the acquire never entered the backoff sleep, through a counter or failpoint, instead of comparing elapsed time against a bound. A 116 ms stall on a shared Windows runner doesn't show a sleep. The proposed fix for view.rs is in my earlier comment. No fix PR exists for either yet.
I've used this PR's one re-run, so I'm leaving CI as is. The PR stays watched. A maintainer can re-run the job, or it will re-run with the next push.
Generated by Claude Code
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Oct 9, 2026
|
[final reviewer] ci-ok was red on 038042f4 from one Windows unit test, patch::apply_lock::tests::zero_timeout_does_not_sleep_before_held (test (windows-latest, 1), run 37862006667, attempt 2): the non-blocking acquire returned the right LockError::Held but took 116 ms against the test's 100 ms limit. That test is unchanged on main and this PR doesn't touch apply_lock.rs (its only file is crawlers/nuget_crawler.rs), so this isn't this PR's failure. The 100 ms limit equals the backoff sleep it guards against, which leaves no room on a busy Windows runner. That is a CI-reliability fix for the janitor, and no fix exists yet.
I merged main into the branch (now 5ea0f7a9, a merge commit only, so the approval on 038042f4 still covers the code) to get a fresh CI run. Locally the 40 nuget_crawler unit tests pass after the merge. Once ci-ok and clippy are green, the next final-review pass sends it to the merge queue.
Generated by Claude Code
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Oct 9, 2026
This was referenced Nov 4, 2026
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Labels
arch-refactor
PR opened by the scheduled architecture refactor routine
refactor
Structural change: duplicated code or logic, missing abstraction, layering, dead code
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|