Repository navigation
Fix berry restore keeping removed descriptors (#1435) - #1436
Merged
Mikola Lysenko (mikolalysenko) merged 3 commits intoOct 12, 2026
Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
Mikola Lysenko (mikolalysenko) merged 3 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A hosted yarn berry pin routes each descriptor of the patched package through its own root `resolutions` selector. After `yarn remove` in one workspace member (another member still using the package), rollback and remove rebuilt the restored lock key from every selector, putting the removed member's descriptor back. Every `yarn install --immutable` then failed with YN0028 even though the command reported success. The restore now keys the entry only by the descriptors some lock entry still declares (a dependency map, in yarn 4's `npm:` or yarn 2/3's bare spelling, or a `patch:` wrapper). All routed selectors are still dropped. If the lock declares none of them, the old behavior is kept. Fixes #1435 Assisted-by: Claude Code:claude-opus-5-5
Read what the lock still declares once per restore, as a set of exact descriptors from dependency maps and `patch:` wrappers, instead of a substring scan per selector. A scoped namesake (`@acme/left-pad`) or a longer range (`1.3.0-rc.1`) no longer counts as declaring `left-pad@npm:1.3.0`, so its stale selector can't slip back into the restored key. Refs #1435 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 12, 2026 16:57
Collaborator
Author
|
BugBot review Generated by Claude Code |
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 3977e12. Configure here.
|
Bugbot Autofix prepared a fix for the issue found in the latest run.
Or push these changes by commenting: Preview (95f8bed94a)diff --git a/crates/socket-patch-core/src/formats/yarn/berry_prune.rs b/crates/socket-patch-core/src/formats/yarn/berry_prune.rs
--- a/crates/socket-patch-core/src/formats/yarn/berry_prune.rs
+++ b/crates/socket-patch-core/src/formats/yarn/berry_prune.rs
@@ -60,6 +60,44 @@
out
}
+/// The descriptors (`name@range`) an entry's `dependencies:` and
+/// `peerDependencies:` maps name. Yarn keys a lock entry by the union of
+/// every descriptor that resolved to it, including peer requests.
+pub(crate) fn dependency_and_peer_descriptors(stanza: &str) -> Vec<String> {
+ let mut out = Vec::new();
+ let mut in_deps = false;
+ for line in stanza.lines().skip(1) {
+ if let Some(field) = line.strip_prefix(" ").filter(|r| !r.starts_with(' ')) {
+ in_deps =
+ field.trim_end() == "dependencies:" || field.trim_end() == "peerDependencies:";
+ continue;
+ }
+ if !in_deps {
+ continue;
+ }
+ let Some(rest) = line.strip_prefix(" ").filter(|r| !r.starts_with(' ')) else {
+ continue;
+ };
+ let (name, range) = match rest.strip_prefix('"') {
+ Some(quoted) => match quoted.split_once("\": ") {
+ Some((name, range)) => (name, range),
+ None => continue,
+ },
+ None => match rest.split_once(": ") {
+ Some((name, range)) => (name, range),
+ None => continue,
+ },
+ };
+ let range = range.trim_end();
+ let range = range
+ .strip_prefix('"')
+ .and_then(|r| r.strip_suffix('"'))
+ .unwrap_or(range);
+ out.push(format!("{name}@{range}"));
+ }
+ out
+}
+
/// The descriptors `before` depends on that `after` (the same entry,
/// rewritten) no longer does: the edges a rewrite cut.
pub(crate) fn cut_descriptors(before: &str, after: &str) -> Vec<String> {
diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/npm.rs b/crates/socket-patch-core/src/patch/redirect/upstream/npm.rs
--- a/crates/socket-patch-core/src/patch/redirect/upstream/npm.rs
+++ b/crates/socket-patch-core/src/patch/redirect/upstream/npm.rs
@@ -1068,13 +1068,13 @@
}
/// The descriptors a berry lock's `stanzas` still declare: every
-/// entry's `dependencies:` map, plus the descriptor a `patch:` descriptor
-/// wraps (`name@patch:name@npm%3A1.3.0#…` wraps `name@npm:1.3.0`), in
-/// either a dependency map or a key. Yarn 4 spells a registry range
-/// `npm:range`, yarn 2 and 3 a bare `range`; see
+/// entry's `dependencies:` and `peerDependencies:` map, plus the
+/// descriptor a `patch:` descriptor wraps (`name@patch:name@npm%3A1.3.0#…`
+/// wraps `name@npm:1.3.0`), in either a dependency map or a key. Yarn 4
+/// spells a registry range `npm:range`, yarn 2 and 3 a bare `range`; see
/// [`berry_descriptor_declared`].
fn berry_declared_descriptors(stanzas: &[String]) -> BTreeSet<String> {
- use crate::formats::yarn::berry_prune::dependency_descriptors;
+ use crate::formats::yarn::berry_prune::dependency_and_peer_descriptors;
use crate::formats::yarn::patterns::split_berry_key_patterns;
use crate::formats::yarn::stanzas::stanza_key;
@@ -1083,7 +1083,7 @@
let keys = stanza_key(stanza)
.map(split_berry_key_patterns)
.unwrap_or_default();
- for descriptor in dependency_descriptors(stanza) {
+ for descriptor in dependency_and_peer_descriptors(stanza) {
if let Some(inner) = berry_patch_inner(&descriptor) {
out.insert(inner);
}You can send follow-ups to the cloud agent here. |
Tanmay Singla (Tanmay182003)
approved these changes
Oct 12, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-berry-restore-stale-selectors
branch
October 12, 2026 17:58
This was referenced Oct 12, 2026
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 #1435
Summary
After a hosted yarn berry pin, a
yarn removein one workspace member(while another member still uses the package) followed by
socket-patch rollbackorremove <pkg>exited 0. It left a lock thatevery
yarn install --immutablerejected with YN0028. Rollback andremove now restore the registry entry keyed only by the descriptors the
project still declares, so the next immutable install passes.
Root cause
A hosted berry pin routes each descriptor of the patched package through
its own root
resolutionsselector and keys the lock entry by thetarball URL.
restore_berry(
crates/socket-patch-core/src/patch/redirect/upstream/npm.rs) rebuiltthe restored key from every selector routed to that URL. It never
checked whether any lock entry still declares that descriptor. #1321
(
retire_stale_selectors) only covers a URL entry that is gone entirely.Yarn keeps each dependent's original descriptor in its lock
dependencies:map (checked with yarn 4.9.1: after theyarn remove,member
a's entry losesleft-pad: "npm:1.3.0"andbkeepsleft-pad: "npm:^1.3.0"), so the lock itself says which descriptorsare still in use.
Fix
berry_declared_descriptorsreads, once per restore, the exactdescriptors the lock declares. These come from every dependency map
and from the descriptor a
patch:descriptor wraps, in a key or adependency map.
berry_descriptor_declaredmatches yarn 4'snpm:rangeand yarn 2/3's barerangespellings.selectors are still removed from
package.json.previous behavior is kept, so nothing that restores today regresses.
Tests
in_process_redirect::yarn_berry_unwind_drops_a_descriptor_no_entry_declares(rollback andremove <purl>, stale selector undeclared vs declared by a real workspace member)"in-proc-redirect@npm:1.0.0, in-proc-redirect@npm:^1.0.0"npm::tests::berry_descriptor_declared_reads_what_the_lock_still_uses(yarn 4/3 spellings,patch:key and dep, alias, scoped namesake, prerelease range)Real-yarn check (yarn 4.9.1, the issue's two-workspace repro with a local
tarball server): with the old two-descriptor key
yarn install --immutablefails YN0028 (-"left-pad@npm:1.3.0, left-pad@npm:^1.3.0"/
+"left-pad@npm:^1.3.0"). With the key this PR writes, it passes.Local:
cargo fmt --all -- --checkok;cargo clippy --workspace --all-features -- -D warningsok. Suites:socket-patch-core --lib6357 passed,
hosted_inventory39/39,in_process_redirect140/143,in_process_rollback_hosted42/42,hosted_memory_engineandhosted_memory_parityall pass. The 7 failures (4 core, 3in_process_redirect) are chmod-based write-failure tests. They fail the
same way on
mainhere because this sandbox runs as root. A fullcargo test --workspacerun ran out of the sandbox's disk while linking,so I ran these suites instead.
I couldn't run the real-yarn e2e matrix (
scripts/yarn-berry-vex-matrix.sh)here because corepack can't fetch yarn in this sandbox. CI runs it.
/code-review high
I fixed these findings in 3977e12:
referenced(a scoped namesake or alonger range counted as a declaration). Replaced with exact matching.
referenced. The change was reverted.workspacesfield.Not fixed: the silent fallback when no descriptor matches. That fallback
keeps today's behavior for lock spellings this code doesn't read. A
new warning code there would fire only on shapes nobody has reported.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JbxZ6TGnJBvpiAJkB9N2XZ
Generated by Claude Code