Repository navigation
Fix -g runs unpatching vendored NuGet (#489) - #1790
Merged
Mikola Lysenko (mikolalysenko) merged 5 commits intoOct 28, 2026
Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
Mikola Lysenko (mikolalysenko) merged 5 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A NuGet PackageReference project restores into the global packages folder, so a global run's copy of a package the project vendors is the project's own patched install. Since #446, `scan -g --mode agent` and `apply -g` took it over as an agent patch, and `rollback -g` or `remove -g` restored upstream bytes into it. A locked restore never re-extracts it, so the project built unpatched with exit 0 while vex kept attesting. Global runs now leave that copy to the project's vendor ledger when the global folder is one the project restores into: scan skips it as vendored_ownership_retained, apply skips it as vendored, and rollback and remove keep it patched. A global store the project doesn't use is still patched and restored as before. Fixes #489 Assisted-by: Claude Code:claude-opus-5-5
Describe in CLI_CONTRACT.md the one store a global run leaves to the project: a NuGet project's own install in the global packages folder it restores into. Add a real-dotnet capstone: vendor, locked restore, scan -g --mode agent, rollback -g, then a second locked restore must still extract the patched bytes. Refs #489 Assisted-by: Claude Code:claude-opus-5-5
With the fix, scan -g no longer records the project's install, so the rollback leg had no manifest to read. Seed the agent record an older scan -g wrote, so the test proves rollback -g leaves the install patched for users upgrading from an affected version. Refs #489 Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the global-scope NuGet ownership check: - A project that restores into RestorePackagesPath never reads the global packages folder, so its global copies stay the global run's. The check now uses the folders the project's restores recorded, and the global folder only while a project has no restore yet. - Repositories whose projects all live in subdirectories, with no root .NET marker, are covered too. - rollback -g and remove -g now report each copy they leave to the project as a skipped/vendored event and a human [skip] line, instead of skipping it silently. - Runs whose --ecosystems excludes nuget skip the check, and path canonicalization moves onto the blocking pool. Refs #489 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 28, 2026 19:47
Collaborator
Author
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 8c4fb08. Configure here.
Collaborator
Author
|
Ready for review at
Generated by Claude Code |
Tanmay Singla (Tanmay182003)
approved these changes
Oct 28, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-nuget-global-folder-ownership
branch
October 28, 2026 21:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #489
Summary
A global run (
-g/--global-prefix) started inside a vendored NuGet projectno longer takes over, or unpatches, the project's own installed copy.
Root cause
project_state_in_scopetreats a global run's target as separate from thecwd project's install. For NuGet that's wrong: a PackageReference project
restores into the global packages folder (
~/.nuget/packagesorNUGET_PACKAGES), so the "global copy" of a package the project vendors isthe project's patched install. Since #446:
scan -g --mode agentandapply -gtook that copy over as an agent patch;rollback -gandremove -grestored upstream bytes into it.dotnet restore --locked-modenever re-extracts it, because the lock and thenupkg's sha512 still match. So the project built unpatched with exit 0
everywhere while
vexkept attestingnot_affected.Fix
NuGetCrawler::global_roots_shared_with(core) says whether the global run'sNuGet folder is one the project restores into. It uses the
packageFoldersits restores recorded, plus the global folder while some project has no
restore yet. It needs no root-level .NET marker.
commands::vendor_owned_keys(CLI) is the single ownership rule. In projectscope the whole ledger owns. Under global scope only the ledger's NuGet keys
own, and only when that folder is shared and
--ecosystemsselects nuget.--check), rollback and remove use it:vendoredand addsvendored_ownership_retained;vendored;skipped/vendoredevent plus a human
[skip]line;(such as
RestorePackagesPath), keeps Fix -g touching the cwd project's state (#436, #445) #446's behavior.CLI_CONTRACT.mddocuments the exception.Out of scope
The hosted variant (from a comment on #489) predates #446. Agent mode
doesn't yield to hosted pins even in project scope, so fixing it needs a
contract decision on a new skip reason. I split it into #1793.
Test evidence
Red on
main, green on this branch:global_scope_project_state::global_rollback_keeps_a_vendored_nuget_projects_installglobal_remove_keeps_a_vendored_nuget_projects_installglobal_apply_skips_a_vendored_nuget_projects_installglobal_agent_scan_skips_a_vendored_nuget_projects_installe2e_nuget_dotnet_build::nuget_vendored_project_survives_global_agent_scan_and_rollbackglobal_rollback_restores_a_store_the_nuget_project_does_not_usenuget_crawler::tests::global_roots_shared_with_*(3)Commands run locally:
cargo fmt --all -- --check: ok.cargo clippy --workspace --all-features -- -D warnings: ok.cargo test -p socket-patch-cli --all-features --test global_scope_project_state: 47 passed.SOCKET_PATCH_DOTNET_E2E_REQUIRED=1 SOCKET_PATCH_DOTNET_E2E_VERSION=8 cargo test -p socket-patch-cli --all-features --test e2e_nuget_dotnet_build -- --ignored: 4 passed.cargo test -p socket-patch-cli --all-features --test e2e_nuget -- --include-ignored: 22 passed.cargo test --workspace --all-features --no-fail-fast: 11 failures, all outside this change.in_process_redirect×3,repair×2, core lib ×4).e2e_bun_lockb×2 tests needpatches-api.socket.devand a pinned bun.The npm, pypi and gem wrappers only dispatch to the binary, so they need no change.
/code-review high findings not fixed
-gfails open. A corrupt.socket/vendor/state.jsonyields no keys, so the old
-gbehavior returns. Failing closed would refuseevery
-grun (npm included) started in a project with a corrupt ledger,and project-scope commands already report the corruption. Not worth the
blast radius.
remove -greads the ledger once more. One small extra file read on aglobal run, which is otherwise never locked against the vendor flow; it isn't
a correctness issue.
https://claude.ai/code/session_011f9aeT3Qr5oee9inqu1TVJ
Note
Medium Risk
Changes global-scope ownership and rollback/remove/apply behavior for NuGet when cwd is a vendored project; wrong folder detection could still patch or restore the wrong copy, but logic is scoped to NuGet ledger keys and restore-recorded folders.
Overview
Global
-gruns no longer unpatch or take over a vendored NuGet project's own install when that install lives in the global packages folder the project restores into (NUGET_PACKAGES/ default~/.nuget/packages). Previously those commands treated that copy as a separate global target, which could restore upstream bytes thatdotnet restore --locked-modenever refreshed—leaving the project building unpatched while tooling still looked healthy.The fix adds
NuGetCrawler::global_roots_shared_withto detect overlap between the global run's NuGet roots and folders recorded in the project's restores (project.assets.jsonpackageFolders, with sensible fallbacks), andvendor_owned_keysas the single rule for which ledger keys still own copies under global scope (NuGet keys only, when shared and NuGet is in scope).scan,apply,rollback, andremovenow use it: agent global flows skip asvendored/vendored_ownership_retained; rollback/remove leave the copy patched, emitskipped/vendoredevents and human[skip]lines, and rollback keeps manifest records. Unrelated global stores (e.g.RestorePackagesPath) and other ecosystems keep prior #446 behavior.CLI_CONTRACT.mddocuments the NuGet exception.Tests cover in-process global-scope scenarios, NuGet crawler sharing logic, and a real .NET SDK e2e repro.
Reviewed by Cursor Bugbot for commit 8c4fb08. Configure here.
Generated by Claude Code