Describe the bug
ORT's Yarn 2+ analyzer treats multiple co-installed versions of the same package as a fatal resolution error (IllegalStateException: Could not unambiguously resolve locator …), even when the project is in a valid state according to Yarn Berry.
This is especially problematic for packages that Yarn automatically rewrites to builtin compat patches (e.g. resolve via @yarnpkg/plugin-compat). A single logical dependency can legitimately exist as both:
resolve@npm:<version>
resolve@patch:resolve@npm:<version>#optional!builtin<compat/resolve>
... and different parts of the tree may still use different semver majors (e.g. resolve@1.x and resolve@2.0.0-next.x). That is normal Yarn Berry behavior and should not abort analysis.
ORT appears to look up an exact locator (often derived from a lockfile descriptor or patch URL) and fails if that exact identity is not the only installed variant, instead of accepting the installed set Yarn actually linked.
To Reproduce
Steps to reproduce the behavior:
- Use a Yarn 4 (Berry) project with
nodeLinker: node-modules and a dependency that pulls in resolve (e.g. via rechoir → knex, or eslint with resolve@^2.0.0-next).
- Ensure the lockfile contains both plain npm and compat-patched
resolve locators, and optionally multiple majors, e.g.:
resolve@npm:^1.20.0 → 1.22.12
resolve@npm:^2.0.0-next.5 → 2.0.0-next.7
resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve> → …::version=1.22.12&hash=…
resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve> → …::version=2.0.0-next.7&hash=…
- Run ORT analyzer on the project (
ort analyze … with Yarn2 package manager enabled).
- See error similar to:
Yarn 2+ failed to resolve dependencies for path '<package>/package.json': IllegalStateException: Could not unambiguously resolve locator 'resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin<compat/resolve>::version=2.0.0-next.5&hash=c3c19d'. Found 4 installed versions of module 'resolve': [resolve@npm:1.22.12, resolve@npm:2.0.0-next.7, resolve@patch:resolve@npm%3A1.22.12#optional!builtin<compat/resolve>::version=1.22.12&hash=c3c19d, resolve@patch:resolve@npm%3A2.0.0-next.7#optional!builtin<compat/resolve>::version=2.0.0-next.7&hash=c3c19d]
Expected behavior
- ORT should not fail analysis when Yarn Berry has successfully installed a dependency tree with multiple versions or multiple locators for the same package name.
- The analyzer should map lockfile descriptors / patch locators to the actually installed packages (as
yarn why / the lockfile resolution: lines show), similar to how npm/yarn tolerate multiple versions in large monorepos.
- Builtin compat patches (
optional!builtin<compat/resolve>) should be first-class citizens in locator resolution, not treated as "missing" versions.
Console / log output
We have several of those:
Yarn 2+ failed to resolve dependencies for path '<package>/package.json': IllegalStateException: Could not unambiguously resolve locator 'resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin<compat/resolve>::version=2.0.0-next.5&hash=c3c19d'. Found 4 installed versions of module 'resolve': [resolve@npm:1.22.12, resolve@npm:2.0.0-next.7, resolve@patch:resolve@npm%3A1.22.12#optional!builtin<compat/resolve>::version=1.22.12&hash=c3c19d, resolve@patch:resolve@npm%3A2.0.0-next.7#optional!builtin<compat/resolve>::version=2.0.0-next.7&hash=c3c19d]
Corresponding <package>/yarn.lock entries:
"resolve@npm:^1.20.0":
resolution: "resolve@npm:1.22.12"
"resolve@npm:^2.0.0-next.5":
resolution: "resolve@npm:2.0.0-next.7"
"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>":
resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin<compat/resolve>::version=1.22.12&hash=c3c19d"
"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve>":
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.7#optional!builtin<compat/resolve>::version=2.0.0-next.7&hash=c3c19d"
yarn install succeeds; yarn why resolve shows expected patched/unpatched usage.
Environment
Based on a run from @oheger-bosch - i hope he has the details.
Additional context
- Yarn Berry's
@yarnpkg/plugin-compat automatically applies optional!builtin<compat/resolve> to resolve dependencies (plugin-compat README). This is unrelated to user-defined resolutions and cannot be removed without disabling compat.
- Even with
resolutions pinning resolve to a single version, both resolve@npm:<ver> and resolve@patch:…::<ver> can remain in the lockfile — ORT still errors.
- Related upstream work: ort#8058, ort#11208.
- Workaround today: per-package
resolutions to collapse semver descriptor buckets (e.g. "debug": "^4.4.3", "@babel/parser": "^7.29.3"). This is fragile, does not scale across large monorepos, and does not fix the npm + compat-patch dual-locator case for resolve.
- We hit this across 50+
package.json workspaces in a Yarn 4 monorepo during OSS compliance (OCaaS) scanning.
Describe the bug
ORT's Yarn 2+ analyzer treats multiple co-installed versions of the same package as a fatal resolution error (
IllegalStateException: Could not unambiguously resolve locator …), even when the project is in a valid state according to Yarn Berry.This is especially problematic for packages that Yarn automatically rewrites to builtin compat patches (e.g.
resolvevia@yarnpkg/plugin-compat). A single logical dependency can legitimately exist as both:resolve@npm:<version>resolve@patch:resolve@npm:<version>#optional!builtin<compat/resolve>... and different parts of the tree may still use different semver majors (e.g.
resolve@1.xandresolve@2.0.0-next.x). That is normal Yarn Berry behavior and should not abort analysis.ORT appears to look up an exact locator (often derived from a lockfile descriptor or patch URL) and fails if that exact identity is not the only installed variant, instead of accepting the installed set Yarn actually linked.
To Reproduce
Steps to reproduce the behavior:
nodeLinker: node-modulesand a dependency that pulls inresolve(e.g. viarechoir→knex, oreslintwithresolve@^2.0.0-next).resolvelocators, and optionally multiple majors, e.g.:resolve@npm:^1.20.0→1.22.12resolve@npm:^2.0.0-next.5→2.0.0-next.7resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>→…::version=1.22.12&hash=…resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve>→…::version=2.0.0-next.7&hash=…ort analyze …with Yarn2 package manager enabled).Expected behavior
yarn why/ the lockfileresolution:lines show), similar to how npm/yarn tolerate multiple versions in large monorepos.optional!builtin<compat/resolve>) should be first-class citizens in locator resolution, not treated as "missing" versions.Console / log output
We have several of those:
Corresponding
<package>/yarn.lockentries:yarn installsucceeds;yarn why resolveshows expected patched/unpatched usage.Environment
Based on a run from @oheger-bosch - i hope he has the details.
Additional context
@yarnpkg/plugin-compatautomatically appliesoptional!builtin<compat/resolve>toresolvedependencies (plugin-compat README). This is unrelated to user-definedresolutionsand cannot be removed without disabling compat.resolutionspinningresolveto a single version, bothresolve@npm:<ver>andresolve@patch:…::<ver>can remain in the lockfile — ORT still errors.resolutionsto collapse semver descriptor buckets (e.g."debug": "^4.4.3","@babel/parser": "^7.29.3"). This is fragile, does not scale across large monorepos, and does not fix the npm + compat-patch dual-locator case forresolve.package.jsonworkspaces in a Yarn 4 monorepo during OSS compliance (OCaaS) scanning.