Skip to content

pacquet: linkWorkspacePackages ignored for bare-semver deps (workspace packages bypassed; can wrong-install or 404) #11929

Description

@zkochan

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pacquet version

0.2.9 (also reproduces on 0.2.8).

Which area(s) of pnpm are affected?

pacquet resolver (resolving-npm-resolver).

Describe the Bug

Pacquet's npm resolver doesn't honor linkWorkspacePackages in pnpm-workspace.yaml. When a workspace package depends on another workspace package via a bare semver range (e.g. "foo": "^1.0.0", no workspace: prefix), pacquet always goes to the registry instead of preferring the matching workspace package.

Symptoms:

  • If a same-named package exists on npm at a matching version, pacquet silently links the wrong package (registry copy, not the local workspace one).
  • If no same-named npm package exists, the install fails with a 404 from the registry.

The workspace:-prefixed branch works correctly; only the bare-semver path is missing.

Why this matters

  • vlt.sh chart babylon column is DNF on every variation (see pacquet#11902). Babylon's root package.json lists @dev/build-tools: ^1.0.0 (a workspace package), and @dev/build-tools doesn't exist on the public npm registry — so pacquet's resolver 404s and the install errors out instantly.
  • Silent-wrong-install risk. Any user whose local workspace package shares a name with an npm package would have their local copy bypassed without warning.

Repro

Minimal — name chosen so npm can't accidentally satisfy it.

package.json:

{
  "name": "root",
  "private": true,
  "devDependencies": {
    "local-only-pkg-xyz999": "^1.0.0"
  }
}

pnpm-workspace.yaml:

linkWorkspacePackages: true
packages:
  - "packages/*"

packages/local-only-pkg-xyz999/package.json:

{
  "name": "local-only-pkg-xyz999",
  "version": "1.0.0",
  "private": true
}
$ pnpm install --ignore-scripts --silent
$ readlink node_modules/local-only-pkg-xyz999
../packages/local-only-pkg-xyz999          # ← workspace match, correct

$ pacquet install
Error: pacquet_package_manager::resolve_importer
  × installing dependencies
  ├─▶ Failed to resolve importer: Failed to resolve dependency: Failed to
  │   fetch metadata from https://registry.npmjs.org/local-only-pkg-xyz999:
  │   HTTP status client error (404 Not Found)

Larger repro: the babylon fixture at https://github.com/vltpkg/benchmarks/tree/main/fixtures/babylon reproduces the same error on every variation.

Root cause

Pacquet's NpmResolver::resolve_impl only routes workspace:-prefixed specs through try_resolve_from_workspace. For bare semver specs, it falls straight through to pick_package against the registry without ever consulting workspace_packages.

The LinkWorkspacePackages field exists on PnpmSettings but is never read by the resolver.

Pnpm's three workspace branches (port targets)

In pnpm's resolving/npm-resolver/src/index.ts:

  1. workspace: prefix (line 417 / tryResolveFromWorkspace). ✅ Already ported.
  2. Registry-pick succeeds + workspace match (line 550). When the registry returned a real version and a workspace package matches the same (name, version), prefer the workspace one. ❌ Missing in pacquet.
  3. Registry-pick fails / no matching version (line 507 / 528 — tryResolveFromWorkspacePackages). On 404 or NoMatchingVersion, fall back to the workspace if a matching package exists. ❌ Missing in pacquet — this is the branch that would unblock babylon.

The gate that controls whether branches 2 and 3 fire is opts.alwaysTryWorkspacePackages !== false (line 435), which pnpm wires from the link-workspace-packages config:

  • true (default) → both branches active.
  • false → workspace packages only matched on the workspace: prefix path.
  • "deep" → also threaded through transitive resolution.

Implementation hints

  • Plumb link_workspace_packages from PnpmSettings through to ResolveOptions (ResolveOptions::always_try_workspace_packages).
  • Add a tryResolveFromWorkspacePackages equivalent to resolving-npm-resolver (sibling of the existing try_resolve_from_workspace).
  • In NpmResolver::resolve_impl, wire branches 2 and 3 around the existing pick_package call, mirroring pnpm's structure.
  • Port the upstream tests at resolving/npm-resolver/test/ that cover linkWorkspacePackages so behavior parity is verifiable.

Cardinal-rule reminder

Per pacquet/AGENTS.md: match pnpm's structure, edge cases, and config-resolution semantics. linkWorkspacePackages accepts true, false, "deep"; the field on PnpmSettings is typed Option<serde_json::Value> for that reason — preserve the three-way semantics, not just the boolean.


Written by an agent (Claude Code, claude-opus-4-7).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions