Skip to content

Fix cargo agent mode patching overridden crates (#506) - #1715

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-cargo-agent-non-registry-lock-source
Oct 26, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-cargo-agent-non-registry-lock-source

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #506

Summary

In a Cargo project, agent mode and vex no longer patch or attest a
crate's copy in the shared $CARGO_HOME/registry/src when the project's
Cargo.lock takes that crate from a git, path or [patch] source. Cargo
never builds that copy. Before this change, agent mode could patch it and
vex could emit a false not_affected for code the build doesn't
contain.

Root cause

Apply (find_all_packages_for_purls) and vex
(find_manifest_package_copies_reusing) find a Cargo crate by looking up
<name>-<version> in every registry index dir
(CargoCrawler::find_by_purls), by name and version alone. They never
asked whether the lock takes that crate from a registry. The scan crawl
was already scoped to the lock's registry-sourced entries (lock_scope,
#1662), but these two lookups were not.

What main did with each shape, from the red runs:

Shape apply on main vex on main
[patch.crates-io] path in Cargo.toml skipped (user_patch_conflict) attests not_affected
same in .cargo/config.toml skipped (user_patch_conflict) attests
[patch] + --allow-patch-conflicts patches unused copy, success attests
git dependency patches unused copy, success attests
path dependency patches unused copy, success attests
[replace] (lock keeps registry source + replace key) skipped; patched with --allow-patch-conflicts attests

Fix

Tests (red → green)

Issue / case Test Before fix After
#506 apply, git and path deps apply::cargo_lock_source::crate_overridden_away_from_the_registry_is_not_patched FAILED (registry copy patched) ok
#506 apply, [patch] + --allow-patch-conflicts apply::cargo_lock_source::allowed_patch_conflict_still_leaves_the_unused_registry_copy FAILED ok
#506 [replace] (review) same apply + vex tests, Replace shape; core replaced_registry_entry_is_out_of_scope FAILED (patched / attested) ok
#506 vex, all override shapes e2e_vex::verify_mode_does_not_attest_cargo_crate_overridden_away_from_registry FAILED (attested) ok
controls: registry-sourced crate still patched and attested registry_sourced_crate_is_patched_in_the_registry_cache, verify_mode_attests_registry_sourced_cargo_crate ok ok

Commands run locally:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test --workspace --all-features --no-fail-fast: 14,911 passed, 11
    failed. All 11 are environmental and in code this PR doesn't touch. The
    container runs as root, so the chmod-based write-failure tests in
    in_process_redirect, repair and core vendor::pypi_* /
    vlt_heal / copy_tree can't fail their writes. The two
    e2e_bun_lockb failures come from a blocked network blob fetch and the
    local bun 1.4.4. CI runs these on its own runners.
  • Ignored cargo e2e: e2e_safety_cargo_build -- --ignored 10/10 pass.
    e2e_redirect_cargo_build, e2e_redirect_cargo_shapes,
    e2e_vendor_cargo_build and mode_migration_cargo have no ignored tests;
    their regular tests passed in the workspace run.
  • No wrapper changes are needed: npm/, pypi/ and gem/ only dispatch
    to the binary.

Review notes (/code-review high), not fixed here

  • Workspace member cwd: the scope reads <cwd>/Cargo.lock only, so
    from a member directory with no lock of its own the filter doesn't apply.
    This is the same limitation crawl_all already has. Walking up to an
    ancestor lock risks scoping a nested non-member project to an unrelated
    lock, so it needs its own change. Follow-up.
  • get's installed-version narrowing still uses the unscoped rollback
    lookup, so get can record a patch that apply then reports as not
    installed. Nothing gets patched or attested wrongly. Follow-up.
  • Git/path dependencies fail an all-miss apply (exit 1) instead of a
    calm skip. This is deliberate: the build stays unpatched, which matches
    vendored mode refusing the same project. The detail now says why.
  • .cargo/config paths = [...] overrides leave no trace in
    Cargo.lock, so this lock-based scope can't see them. Plain apply
    already skips them through the user-patch detector, but vex doesn't
    consult it. Follow-up.
  • Perf: the filter re-reads the cargo config and Cargo.lock once per
    apply/vex run. That's minor, and only for runs with Cargo purls.

https://claude.ai/code/session_01HAS6MmhgCPZmdDxkfHZmMG


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
Regression tests for #506: apply and vex must ignore a registry-cache
copy of a crate that Cargo.lock takes from a [patch.crates-io] path
(in Cargo.toml or .cargo/config.toml) or a git source, since cargo
never builds that copy. A registry-sourced control still patches and
attests.

Assisted-by: Claude Code:claude-opus-5-5
Agent-mode apply and vex looked a crate up in the shared
$CARGO_HOME/registry/src by name and version alone. When Cargo.lock
takes the crate from a git dependency, apply patched a registry copy
cargo never builds and reported success. When the crate came from a
[patch] path override, vex hash-verified that unused copy and attested
not_affected for code the build does not contain.

Both lookups now keep only crates the lock takes from a registry, the
same scope the scan crawl already uses. Rollback and scan --prune still
see the copy, so a patch an earlier apply left there can be restored.
The cargo not-installed message now names this case besides the
cargo fetch remedy.

Fixes #506

Assisted-by: Claude Code:claude-opus-5-5
Address review of the #506 fix: test a plain path dependency and a
[patch] override that --allow-patch-conflicts lets apply try, since
the lock-source filter alone decides those. Share one lock-scope
helper between the scan crawl and the new lookup so the two can't
drift, and say in the docs which code each override shape gets.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 24, 2026 17:16
@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.

Stale Bugbot comment from a previous run.

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

Copy link
Copy Markdown
Collaborator Author

Ready for review (burn-down agent)

  • Head: 4ca9004c496baf02567dbd1cff8bb7f710b0e06b
  • CI: 38/38 non-skipped checks green (50 skipped by path filters), no failures. Mergeable, no conflicts.
  • Bugbot: reviewed 4ca9004 and found no new issues. No open review threads.
  • CHANGELOG.md untouched.
  • For reviewers: the "Review notes" section of the description lists the known limitations left as follow-ups (workspace member cwd without its own Cargo.lock, get's unscoped installed-version narrowing, git/path deps failing an all-miss apply on purpose).

Slack announcement wasn't sent in this run; a later run will retry.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: In a local Cargo project, agent-mode apply and vex now only use the registry-cache copy of a crate when Cargo.lock takes that crate from a registry. A crate the lock takes from a path, git or [patch] source is no longer patched in (or attested from) the shared $CARGO_HOME/registry/src copy that cargo never builds (#506). The package_not_installed detail and hint now mention this case.

Risk: Low. The filter reuses the lock scope scan already uses (#1662) and only runs for local projects with a readable Cargo.lock. Global runs, cargo vendor sources and lock-less dirs are unchanged. Rollback and scan --prune still see the copy, so patches applied before this change can still be undone.

Look here:

Verified:

  • Read the full diff and every caller of both changed lookups. Rollback paths don't go through the filter.
  • Ran apply::cargo_lock_source (3/3), the cargo e2e_vex tests (2/2) and the core cargo crawler tests. All pass.
  • With the two source files reverted to main, the two new apply tests fail. So the tests do exercise the fix.
  • CHANGELOG.md is untouched.
  • CI on head: 38 success, 50 skipped, 0 failed. ci-ok and clippy are green. Cursor Bugbot: success. No open review threads.

Changes I made: none.

Open questions: none blocking. One note: when --cwd is a workspace member that has no Cargo.lock of its own, nothing is filtered. That is the same behaviour scan's lock scope already has.

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


Generated by Claude Code

Comment thread crates/socket-patch-core/src/crawlers/cargo_crawler.rs Outdated
A [replace] override leaves the crate's Cargo.lock entry with its
registry source and only adds a replace key naming the copy cargo
builds. The lock-scope check counted that entry as registry-sourced,
so vex still attested the unused registry copy and apply with
--allow-patch-conflicts still patched it. The lock reader now reads
the replace key, and replaced entries are out of scope.

Reported in review of #1715.

Assisted-by: Claude Code:claude-opus-5-5
@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 ed601ac. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[final reviewer] Disabled auto-merge. This PR was armed only on the strength of the final-review brief, and the head has gained non-merge commits since that brief (review fixes pushed today). I'll write a fresh brief once those pushes settle and CI and Bugbot are green on the new head, then re-arm. The Ready for review label is still on; the burn-down should confirm it against the new head.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: Ready for review at ed601ac.

  • CI: 56/56 checks on ed601ac passed or skipped (37 success, 19 skipped); mergeable, no conflicts. No main-wide failures. CHANGELOG.md untouched.
  • Bugbot: reviewed ed601ac, no findings; all review threads resolved.
  • Re-confirmed at the new head (previous label comment was at 4ca9004). Tanmay's review thread is resolved. Auto-merge was disabled by the final reviewer pending a fresh brief.
  • Slack announcement not sent this run (Slack send unavailable); the next run will retry.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: apply (including --check) and vex now patch or attest a shared $CARGO_HOME/registry/src crate copy only when the project's Cargo.lock takes that name@version from a registry+ or sparse+ source and not through [replace] (#506). A crate the lock takes from a path, git, [patch] or [replace] source is reported user_patch_conflict or package_not_installed instead of being patched in a copy the build never uses. rollback and scan --prune are deliberately left unfiltered.

Risk: low to medium.

  • When there is no Cargo.lock, the lock can't be parsed, or the run is global or a cargo vendor dir, the filter does nothing, so behaviour stays as on main.
  • Visible changes:
    • --allow-patch-conflicts on a [patch]/[replace] crate now exits 1 with package_not_installed (it used to "succeed" on the unused copy).
    • vex exits non-zero for these crates.
    • scan stops listing [replace]d crates.
    • The cargo not-installed detail and hint text gained a sentence.

Look here

Verified

  • A subagent read the full diff.
  • CI's clippy command (--workspace --all-features -D warnings) and cargo fmt --check are clean.
  • Tests passed:
    • core --lib cargo: 467
    • --test apply: 156 (1 ignored)
    • --test e2e_vex: 45
    • cli --lib (cargo / dispatch / vex): 90
  • With retain_locked_cargo_copies turned into a no-op, the 3 new apply/vex tests fail. With the replace.is_none() check dropped, replaced_registry_entry_is_out_of_scope fails. So the tests cover the change.
  • CI is green on ed601ac (ci-ok and clippy). Bugbot is clean and there are no open threads. CHANGELOG.md is untouched.

Changes I made: none.

Open questions (none blocking; follow-up material)

  • The detail and hint text say "path, git or [patch] source" and leave out [replace]. They also now add the override sentence to every cargo "not fetched yet" message.
  • lock_scope reads only <cwd>/Cargo.lock. Run from a workspace member directory, the filter does nothing (it fails open, so behaviour is as on main).
  • LockedRegistryCrates keys on (name, version) without the source. That only matters when the same version is pulled from two registries.

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


Generated by Claude Code

Merged via the queue into main with commit a2253e0 Oct 26, 2026
56 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-cargo-agent-non-registry-lock-source branch October 26, 2026 18:37
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

3 participants