Skip to content

Fix hosted yarn classic pins missing berry warning (#907) - #917

Merged
Mikola Lysenko (mikolalysenko) merged 9 commits into
mainfrom
agent/fix-yarn-classic-hosted-berry-risk
Oct 7, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 9 commits into
mainfrom
agent/fix-yarn-classic-hosted-berry-risk

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #907

Summary

A yarn 2+ (berry) install migrates a classic (v1) yarn.lock and re-resolves every entry from the registry. That drops a hosted pin exactly the way it drops vendored wiring, and the package then installs unpatched with nothing printed. Vendored mode already warned about this (yarn_classic_berry_migration_risk). Hosted scan / get reported success with no warning, even when package.json declared "packageManager": "yarn@4.x".

Hosted runs now warn redirect_yarn_classic_berry_migration_risk (in redirect.warnings) once per run when the classic lock carries a hosted pin, whether it was written this run or is already there from an earlier one. A "packageManager": "yarn@1…" pin suppresses it, as it does for vendored. The warning is advisory only: the pin still lands and the exit code is unchanged.

Root cause

  • rewrite_yarn_classic (core/src/patch/redirect/mod.rs) never ran the check that the vendored probe (vendor::yarn_classic_berry_migration_risk) runs.
  • The hosted engine's read_candidate_files (core/src/hosted/engine.rs) read the root package.json only next to an npm lock or a berry yarn.lock. So the classic rewriter couldn't see packageManager at all.

Changes

  • vendor::manifest_pins_yarn_classic: the yarn@1 check, pulled out of the vendored probe so both modes agree (yarn@10 still doesn't match yarn@1; a malformed manifest vouches for nothing).
  • The engine now also reads package.json as advisory input (never rewritten) next to a classic yarn.lock.
  • rewrite_yarn_classic emits the warning when any dep matched a lock entry, the root manifest is present, and it doesn't pin yarn 1. With no root manifest there is no project for yarn to install, so it stays silent. This also keeps the advisory off rewriter fixtures that carry only a lock.
  • Docs: CLI_CONTRACT.md (npm-family flavor coverage) and docs/ecosystems.md.
  • Bench: the yarn-classic scan fixture now declares packageManager: yarn@1.22.22, the same way the berry fixture declares yarn 4. The bench counts any warning as a failed scenario, and the measured scan is unchanged.
  • Ported Route Gradle digests through utils::digest #878 (66009a0, Gradle digests through utils::digest). It fixes production_digests_go_through_the_helpers, which is red on main (9c43dfc) and fails coverage. It becomes a no-op once Route Gradle digests through utils::digest #878 lands.

Tests (red → green)

Per-issue checklist:

Red: with the rewriter warning disabled, both core warn tests and the CLI warn test fail. With the rewriter restored but the engine's package.json read reverted, the CLI warn test still fails, so both halves are needed. Green with the fix.

Commands run locally (Linux, toolchain 1.93.1):

  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core --all-features: all pass except 4 chmod-based tests (copy_tree, vlt_heal, pypi_poetry, pypi_requirements write-failure tests). Those fail only because this sandbox runs as uid 0, and they're in files this PR doesn't touch.
  • CLI suites covgap_commands_scan_hosted, covgap_commands_rollback, covgap_commands_scan_mod, e2e_redirect_yarn_classic_build, e2e_vendor_yarn_classic_build, e2e_vex_redirect, global_scope_project_state, in_process_get_modes, in_process_rollback_hosted, mode_migration_npm, hosted_memory_engine, hosted_memory_parity, scan: pass. covgap_commands_vendor (3) and in_process_redirect (3) have only chmod-0o555 write-failure tests failing, for the same uid-0 reason.
  • scripts/yarn-classic-vex-matrix.sh 1.22.22 (real yarn, required): 40/40 cells PASS
  • socket-patch-bench run -f yarn-classic: both scenarios validate
  • cargo test -p socket-patch-bench: pass
  • cargo fmt isn't enforced by CI and main isn't fmt-clean, so only the new code was formatted.

No wrapper changes: the npm/pypi/gem wrappers only dispatch to the binary.

🤖 Generated with Claude Code


Generated by Claude Code


Note

Low Risk
Advisory warning-only change to hosted yarn classic redirect behavior; no change to pinning logic or exit codes beyond new optional warnings in JSON/stderr.

Overview
Fixes #907: hosted scan/get now surfaces the same yarn classic → berry migration trap vendored mode already warned about.

When a v1 yarn.lock carries hosted pins, the yarn classic rewriter emits redirect_yarn_classic_berry_migration_risk once per run (dry run, wet run, and idempotent re-run). The pin still applies and exit code stays 0; it's advisory only. "packageManager": "yarn@1…" suppresses the warning via shared manifest_pins_yarn_classic, refactored from the vendored probe so both modes agree.

The hosted engine now loads root package.json as advisory input beside a classic yarn.lock (not only npm locks / berry locks), so packageManager is visible to the rewriter.

Docs (CLI_CONTRACT.md, docs/ecosystems.md) describe the hosted warning; the yarn-classic bench fixture pins yarn@1.22.22 so benchmarks don't treat the new warning as a failure. Core unit tests and covgap_commands_scan_hosted integration tests cover warn/suppress/silent cases.

Reviewed by Cursor Bugbot for commit e0c158c. Configure here.

Assisted-by: Claude Code:claude-opus-5-5
A yarn 2+ install migrates a classic (v1) yarn.lock and re-resolves
every entry from the registry, so a hosted pin is silently dropped and
the package installs unpatched. Vendored mode already warned about
this; hosted mode said nothing.

The hosted engine now reads package.json beside a classic yarn.lock,
and the classic rewriter warns redirect_yarn_classic_berry_migration_risk
once per run when the lock carries a hosted pin, unless package.json
pins yarn 1 through packageManager. The yarn 1 check is shared with
the vendored probe so both modes agree.

Fixes #907

Assisted-by: Claude Code:claude-opus-5-5
A yarn.lock with no root package.json is not a project yarn installs
from, so there is nothing to warn about. This also keeps the advisory
off rewriter fixtures that carry only a lock.

Assisted-by: Claude Code:claude-opus-5-5
main has failed socket-patch-core's lib tests since Gradle support
(#646) and the digest helpers (#865) both landed. The guard test
production_digests_go_through_the_helpers flags three files #646 added
that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and
patch/sidecars/maven.rs. That breaks test, test-release and coverage on
every open PR.

Each inline sha1/sha256 call now goes through sha1_hex_of or
sha256_hex_of, which compute the same lowercase hex. Behaviour is
unchanged.

Assisted-by: Claude Code:claude-opus-5-5
(cherry picked from commit 659ac2c)
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage failed on aa1d3c3 in socket-patch-core --lib. The cause is utils::digest::tests::production_digests_go_through_the_helpers, which is also red on main (9c43dfc, CI run 37355008322 fails coverage and the test jobs). It isn't caused by this PR. I cherry-picked the existing fix from #878 (659ac2c, which routes the Gradle digests through utils::digest) into this branch as 66009a0. It becomes a no-op once #878 lands. Locally the digest test now passes.


Generated by Claude Code

The scan benchmark treats any warning as a failed scenario. Its yarn
classic project declared no package manager, so a hosted scan now
correctly warns that a yarn 2+ install would drop the pins. Declare
packageManager yarn@1.22.22, as the berry fixture declares yarn 4 and
as a real classic project would. The measured scan is unchanged.

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

Copy link
Copy Markdown
Collaborator Author

Ready for review at 13242189b7.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 7, 2026
Bring the branch up to date with main (1c6c509). CLI_CONTRACT.md
conflicted on the hosted rewriter's candidate-file paragraph: main added
the sbt build files to the read set and a new "Hosted sbt" paragraph,
while this branch added the yarn classic berry-migration advisory. The
edits touch different clauses, so both are kept.

Co-Authored-By: Claude <noreply@anthropic.com>
Pick up the four PRs that landed on main (db83f01) while the previous
merge was being validated. No conflicts.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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.

Main's test jobs are red: production_digests_go_through_the_helpers
fails because PENDING_INLINE_DIGESTS still names gradle_cache.rs,
jvm_jar.rs and sidecars/maven.rs. #955 added them while those files
computed digests inline, and #690 (sbt/Mill/scala-cli) then moved them
onto utils::digest helpers; the two merged independently, so the
ratchet now fails on stale entries on every platform.

Remove the three entries so the list matches the production tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qq8uwZ9NTCZXZRygn7woy7
(cherry picked from commit 6cb46c0)
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage failed on a2a6006 in socket-patch-core --lib, in utils::digest::tests::production_digests_go_through_the_helpers. It isn't caused by this PR. main (db83f01) is red on the same test in coverage and the test jobs: PENDING_INLINE_DIGESTS still lists crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs, which no longer compute digests inline. I cherry-picked the fix from #1016 (6cb46c0, which drops those stale entries) as 494c3bf. It becomes a no-op once #1016 lands. Locally the digest test now passes. #878, ported earlier, was closed without merging; its Gradle change already matches main after the merge.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] The test (macos-latest), test (windows-latest) and test-release failures on a2a6006 are the same socket-patch-core --lib target as coverage (the stale digest pending list, red on main too). 494c3bf fixes them.

sbt 1.3.13 / jdk 8 / hosted also failed on a2a6006 (13 of 14 cases). The same job passes on main (db83f01), and this PR's diff against main doesn't touch the sbt path: the hosted engine change only adds the root package.json read next to a classic yarn.lock for npm candidates. I couldn't read the per-case log, because the artifact download is blocked from this sandbox, so I haven't confirmed the cause. The re-run (attempt 2) was cancelled when 494c3bf was pushed, and that push runs the job again. If it fails there too, I'll treat it as real.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] On 494c3bf, sbt 1.3.13 / jdk 8 / hosted now passes, so its failure on a2a6006 didn't reproduce. A different sbt job, sbt 1.9.9 / jdk 17 / vendored, failed one case, sbt_vendor_subproject_refused. That job passed on a2a6006, and the only change since then is the digest.rs test-list edit from #1016. The same case passes in every other sbt version/JDK cell on this commit, and this PR's diff is npm-only. I can't read the per-case log from this sandbox. I'll re-run the job once, when its workflow run finishes. If it fails again I'll treat it as real.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] The sbt 1.9.9 / jdk 17 / vendored re-run passed on 494c3bf.

native (macos-latest, 1.3.14) failed one Bun cell out of 53: workspace-root vendored on refusalCodesExact. It took 16s against roughly 2s for its neighbours, and the same run had to retry two other cells after "request transport failed". The cell passes on ubuntu and windows on this commit, and on all three OSes on main (db83f01). This PR's vendored change is a behaviour-preserving extraction of the packageManager: yarn@1 check, which only runs when there's a classic yarn.lock. I'll re-run the job once, after its workflow run finishes. A second failure counts as real.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 2da3485 into main Oct 7, 2026
86 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-yarn-classic-hosted-berry-risk branch October 7, 2026 15:26
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

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

Reviewed by Cursor Bugbot for commit e0c158c. Configure here.

Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Blocked: this PR was merged (15:26Z, head e0c158c) while the sweep was still running, so the last Bugbot finding couldn't land here.

  • Conflicts: none. After Fix main CI red on stale digest pending-list entries #1016 merged, I merged origin/main into the branch and pushed e0c158c. The only change from main was utils/digest.rs, which is now identical to main. The earlier 494c3bf push from another agent was integrated, not overwritten.
  • CI on e0c158c at merge time: 354 success, 6 skipped, 2 neutral, 0 failures, about 196 still queued or in progress. On the previous head a2a6006, the only non-digest failure was sbt 1.3.13 / jdk 8 / hosted. That was a Maven/Coursier download flake ("Error downloading org.scala-sbt:...", could not retrieve sbt 1.3.13; the same check passes on main db83f01), and I re-ran it.
  • Bugbot on e0c158c: 1 low-severity finding ("Berry warning fires without a pin"), and it's valid. When an offline mirror refuses the rewrite, rewrite_yarn_classic still set any_pinned from matched_any. So a first run that wrote nothing reported redirect_yarn_classic_berry_migration_risk next to redirect_yarn_classic_offline_mirror.
  • Fix (not merged): branch agent/followup-917-berry-risk-mirror (efc7c38, one commit on top of main). A refused entry now counts as pinned only if an earlier run already wrote its hosted URL into the block. It adds the regression test yarn_classic_berry_risk_follows_pins_under_offline_mirror_refusal, and the core yarn_classic tests plus the digest ratchet pass locally (80/80). I haven't opened a PR for it. A human should open one or pick it up.

Generated by Claude Code

Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 7, 2026
With a yarn-offline-mirror configured the classic hosted rewriter
refuses every entry, so nothing is pinned, yet it still warned that a
berry install would drop the hosted pins (#907's warning counted the
refused entries as pinned). The staged takeover reports a retracted
purl's first rewrite warning as its cause, so a vendored classic
project with a mirror was skipped as redirect_yarn_classic_berry_
migration_risk and the real refusal, redirect_yarn_classic_offline_
mirror, was never reported (in_process_vendor's
classic_vendored_to_hosted_takeover_refuses_with_offline_mirror failed
once main's #917 landed beside the staged takeover).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
github-merge-queue Bot pushed a commit that referenced this pull request Oct 8, 2026
* Let a group commit hold vendored artifact deletions until it lands

A group commit can now defer the vendored artifact deletions a revert
makes (GroupCommit::defer_removals): every per-unit revert removal goes
through remove_tree_and_prune (cargo and golang now too, instead of their
own remove_tree + prune copies) and the bun workspace tarball removal
through remove_mirror, and both queue the deletion for after the commit
when the open group asks for it. A rollback_to forgets the queued
deletions and a dropped group never makes them, so a staged revert can be
undone with its artifact intact.

Also:
- commit_unjournaled: the all-or-nothing replace without the crash
  journal, for runs that must write nothing under .socket/;
- a journal that had to create .socket/vendor/ prunes it again;
- group_commit::exists is public, for overlay-aware existence checks.

Audit B03/B14 groundwork.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Share one takeover-reach predicate between the hosted engines

The disk flow took over cargo, npm, golang, pypi and Gradle maven
entries, while the in-memory engine refused only cargo, npm and golang,
so a vendored PyPI package reached the Python rewriters in memory. Both
now use hosted::takeover::in_reach (audit B15).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Make the vendored-to-hosted takeover staged and atomic

scan/get --mode hosted reverted a vendored package's wiring on disk
first and planned the hosted pin afterwards. When the rewriter then
refused (a lock-level refusal, a missing berry checksum, unavailable
wheel metadata, a Poetry 0.x lock, ...), the package was left unpatched
in both modes, and only six hand-copied per-ecosystem pre-gates tried to
predict those refusals. The dry run counted every takeover as redirected.

The takeover now runs inside the run's group commit:
- each vendored revert is staged in the overlay under a savepoint (a
  failing or drift-keeping revert is rolled back and refused);
- the hosted rewrite reads the overlay, so it plans against the
  reverted project;
- a staged purl the rewrite does not pin is retracted: the overlay goes
  back to its pre-revert state, the purl stays vendored byte for byte
  (redirect_takeover_kept_vendored, skipped with the cause), and the
  rest are staged and rewritten again;
- the hosted pins and the vendored ledger are written into the same
  overlay and committed once (journaled); artifacts go after the commit.
A dry run does the same and drops the overlay, so it reports the wet
outcome. A hosted run without a takeover commits its files unjournaled,
putting back the ones replaced if one fails.

Deleted: the bun, berry (lock and dep), classic, vlt, Gradle, pypi
platform-wheel and requirements pre-gates (9 copies of rewriter logic ->
0; the requirements reach check only explains a retraction now), the
dry-run TakeoverPreview path in the engine, and the stranded-takeover
reporting (redirect_takeover_unpatched), which can no longer happen.

Audit B03, B14, B37 (takeover part).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep a staged Gradle takeover from deleting the vendored tree

The staged vendored-to-hosted takeover runs the real revert inside a
group commit and relies on the overlay plus deferred removals to undo
it. The JVM revert deleted the tree files under .socket/vendor/gradle
and .socket/vendor/maven2 directly, and wrote or deleted the owned
.socket/gradle/.gitattributes, .socket/vendor/.gitattributes and the
derived maven-metadata.xml files straight to disk. A dry run, or a
takeover the hosted Gradle planner refused and retracted, therefore
deleted the vendored jars while the restored wiring still named them.

Capture the owned .gitattributes files and the derived metadata in the
group overlay, and route the tree-file deletions through
group_commit::defer_removal so they happen only after the commit. A
new test stages the revert (with and without a sibling version sharing
the metadata) and checks that dropping or rolling back the group leaves
the project byte-identical and that committing lands the plain revert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Put back direct hosted writes when the hosted commit fails

commit_hosted_writes writes the files the group does not capture (the
Gradle hosted index and script under .socket/gradle/) straight to disk
before the commit. When writing a later file, saving the vendored
ledger or the commit itself failed, the error said nothing was changed
while those files stayed on disk. Record their previous bytes and put
them back on every failure path except an interrupted journaled
commit, which the next locked command finishes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Check that dry-run takeovers leave the whole tree byte-identical

Snapshot every project file, .socket/ and the vendored artifacts
included, around the dry-run vendored-to-hosted takeover for pnpm,
package-lock, vlt, golang and cargo (bun and the uv retract test
already compare the artifact). Rewrite the stale CLI_CONTRACT Gradle
paragraph that still described the deleted takeover_refusal pre-gate,
and the real-Gradle refusal test's doc comment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Make the yarn hosted preflights private to the redirect module

The takeover pre-gates that called preflight_yarn_classic_hosted and
preflight_yarn_berry_hosted from outside are gone. The classic one is
now private and the berry one pub(crate) (upstream/npm.rs still uses
it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Skip the yarn berry risk warning when an offline mirror refused the pins

With a yarn-offline-mirror configured the classic hosted rewriter
refuses every entry, so nothing is pinned, yet it still warned that a
berry install would drop the hosted pins (#907's warning counted the
refused entries as pinned). The staged takeover reports a retracted
purl's first rewrite warning as its cause, so a vendored classic
project with a mirror was skipped as redirect_yarn_classic_berry_
migration_risk and the real refusal, redirect_yarn_classic_offline_
mirror, was never reported (in_process_vendor's
classic_vendored_to_hosted_takeover_refuses_with_offline_mirror failed
once main's #917 landed beside the staged takeover).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep a yarn berry takeover vendored when the project gates refuse it

#657 (merged on main) made the hosted and vendored modes refuse a mixed
root package.json, and gated the vendored-to-hosted takeover before its
revert. The staged takeover dropped that pre-gate and let the hosted
rewriter judge the reverted project, but the berry revert re-renders
package.json in its majority line ending, so a mixed manifest passed
the rewriter's check after the revert and the takeover went ahead
(in_process_vendor berry_takeovers_refuse_before_reverting_the_old_mode
failed after the merge).

Judge the berry project gates once per staging pass, on the pre-revert
overlay, through the rewriter's own preflight_yarn_berry_hosted (the
shared berry_gates set, no copied logic). A refused yarn-berry entry is
skipped with the gate's code, followed by redirect_takeover_kept_vendored.
preflight_yarn_berry_hosted is public again for this caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Keep landed-pin advisories and scope suffixes out of takeover skip reasons

When a staged takeover is retracted and no rewriter warning names the
package, explain() fell back to the rewrite's first warning as the
lock-level cause. That warning can be a success advisory from a pin that
did land (redirect_npm_allow_remote, redirect_pnpm_trust_lockfile,
redirect_yarn_classic_berry_migration_risk), so the skipped purl and
redirect_takeover_kept_vendored reported the wrong code. Skip those
advisories when picking the fallback; with nothing else left the reason
is NOT_PINNED.

names_package accepted `/` as a left boundary unconditionally, so an
unscoped name like `node` matched inside `@types/node` and a retracted
takeover could inherit another package's warning. A `/` now counts as a
boundary only after a path segment, not after an `@scope`.

Co-Authored-By: Claude <noreply@anthropic.com>

* Label the setup-php pin in ci.yml with its real tag

The required 'Audit GHA Workflows' check (zizmor ref-version-mismatch)
now fails on every head because the pinned setup-php hash no longer
matches the moving v2 tag. Same one-line change as #1118, so it merges
cleanly when that lands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hosted yarn classic pins give no berry-migration warning, so a yarn 2+ install silently drops them (vendored warns about the same trap)

3 participants