Visitar URL original
pnpm 12: relative workspace: overrides are not rebased (broken links / injected install failure) · Issue #17010 · pnpm/pnpm · GitHub
Skip to content

pnpm 12: relative workspace: overrides are not rebased (broken links / injected install failure) #17010

Closed

Description

@baymer

Verify latest release

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

pnpm version

12.14.0

Which area(s) of pnpm are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

Save as repro.mjs, put pnpm 12.14.0 on PATH, and run node repro.mjs. The script creates four fresh temporary workspaces, uses only local packages, runs offline, and disables lifecycle scripts. Optionally pass the pnpm executable as the first argument.

import { mkdtempSync, mkdirSync, writeFileSync, readlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';

const pnpm = process.argv[2] || 'pnpm';
const base = mkdtempSync(join(tmpdir(), 'pnpm-relative-workspace-'));
const run = (command, args, cwd) => {
  const result = spawnSync(command, args, {
    cwd, encoding: 'utf8', env: { ...process.env, CI: 'true', NO_COLOR: '1' },
  });
  if (result.error) throw result.error;
  return result;
};
console.log('pnpm:', run(pnpm, ['--version'], base).stdout.trim(), 'Node:', process.version);
for (const injected of [false, true]) {
  for (const protocol of ['workspace', 'file']) {
    const root = join(base, `${protocol}-${injected}`);
    const consumer = join(root, 'packages/deep/consumer');
    const target = join(root, 'packages/target');
    mkdirSync(consumer, { recursive: true });
    mkdirSync(target, { recursive: true });
    const json = (path, value) => writeFileSync(path, JSON.stringify(value));
    json(join(root, 'package.json'), { name: 'root', private: true });
    json(join(consumer, 'package.json'), {
      name: 'consumer', version: '1.0.0', dependencies: { target: '1.0.0' },
    });
    json(join(target, 'package.json'), { name: 'target', version: '1.0.0', main: 'index.js' });
    writeFileSync(join(target, 'index.js'), "module.exports = 'correct-target';\n");
    writeFileSync(join(root, 'pnpm-workspace.yaml'),
      `packages:\n  - packages/**\ninjectWorkspacePackages: ${injected}\noverrides:\n  target: ${protocol}:./packages/target\n`);
    const install = run(pnpm, ['install', '--offline', '--ignore-scripts',
      '--no-frozen-lockfile', '--store-dir', join(base, 'store')], root);
    console.log(`\n${protocol}:, injectWorkspacePackages=${injected}, install exit=${install.status}`);
    if (install.status !== 0) {
      console.log(install.stdout + install.stderr);
      continue;
    }
    console.log('target symlink:', readlinkSync(join(consumer, 'node_modules/target')));
    const resolution = run(process.execPath, ['-p', "require('target')"], consumer);
    console.log(`require('target') exit=${resolution.status}`, resolution.stdout.trim());
    if (resolution.status !== 0) console.log(resolution.stderr.split('\n').slice(0, 8).join('\n'));
  }
}

Describe the Bug

A root override such as target: workspace:./packages/target is not rebased for a nested workspace consumer. Tested on published pnpm 12.14.0, macOS 26.7.1 arm64.

Override injectWorkspacePackages Result
workspace:./packages/target false Install exits 0 but creates a broken symlink; require('target') fails.
workspace:./packages/target true Install fails, looking for packages/deep/consumer/packages/target.
file:./packages/target false Install and require('target') succeed.
file:./packages/target true Install and require('target') succeed.

The intended target is <root>/packages/target. Both packages are workspace members selected by packages/**.

With injection enabled, the error is:

Could not install from "<root>/packages/deep/consumer/packages/target" as it does not exist.
Failed to resolve target@workspace:./packages/target

With injection disabled, packages/deep/consumer/node_modules/target points to ../packages/target, which does not exist. The working file: control points to ../../../target. Disabling injection hides the install-time error but does not fix runtime resolution. Both file: controls also pass a subsequent offline --frozen-lockfile install.

Related but different: #14555 and #14710 concern file: override freshness checks; this report concerns relative workspace: overrides during a fresh install.

Reproduction prepared and executed with Codex.

Expected Behavior

The relative path in the root override should keep referring to /packages/target when applied to each importer, with the resulting link/path rebased for that consumer. It should work with and without injection. file: overrides already preserve the target in this reproduction.

Which Node.js version are you using?

v24.21.0; also reproduced with v26.2.0

Which operating systems have you used?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

No response

Contributing a fix

  • I am working on a fix

Activity

  1. added theissue type on Oct 16, 2026
  2. lazerg commented on Oct 17, 2026

    @lazerg
    Contributor

    I opened #17016 for this. It anchors a workspace:./path override at the workspace root and rewrites it relative to each project, the same way file: and link: overrides work. It fixes both pnpm 11 and pnpm 12, with and without injectWorkspacePackages, and covers the whole issue.


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

  3. added a commit that references this issue on Oct 17, 2026
    5b68320
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions