Fixing ERR_PNPM_WORKSPACE_PKG_NOT_FOUND
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND stops a pnpm install when a manifest asks for a sibling package through the workspace: protocol and pnpm cannot find a workspace package with that name. The dependency is declared correctly in spirit — it is a local package — but pnpm's view of the workspace does not include it. The cause is almost always one of four things: a glob in pnpm-workspace.yaml that misses the folder, a name mismatch, a version range the local package does not satisfy, or an install run from outside the workspace. This guide shows how to tell them apart and fix each.
Exact symptoms and error messages
The error names the importer that declared the dependency and the package it could not find:
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND In apps/web: "@acme/ui@workspace:^" is in the dependencies but no package named "@acme/ui" is present in the workspace
This error happened while installing a direct dependency of /repo/apps/web
Packages found in the workspace: @acme/web, @acme/api, @acme/utils
The last line is the most useful part of the message: it lists every package pnpm discovered. If @acme/ui is missing from that list, discovery is the problem; if it is present under a slightly different name, naming is the problem.
A related error appears when the package is found but its version does not satisfy the range:
ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE In apps/web: No matching version found for @acme/ui@workspace:^2.0.0 inside the workspace
Root cause analysis
pnpm builds its list of workspace packages from the packages globs in pnpm-workspace.yaml, reading the name field of each matched package.json. A workspace: range can only be satisfied from that list — pnpm never falls back to the registry for it, which is exactly what makes the protocol safe. How workspaces are discovered and linked is covered in Workspace Configuration Deep Dive.
The four causes, in order of frequency:
- The folder is not matched by any glob. A new package was created in
libs/uiwhile the file listspackages/*, or it sits two levels deep (packages/shared/ui) under a single-level glob. - The name differs. The folder is
packages/ui, but itspackage.jsonsays"name": "ui"or"@acme/ui-kit". pnpm matches on thenamefield, never on the folder. - The version range does not match.
workspace:^2.0.0requires the local package'sversionto satisfy^2.0.0. A package still at1.9.0fails with the no-matching-version variant. - pnpm is not running in the workspace. Running
pnpm installinside a directory outside the workspace root — or in a Docker build context that copied only one package — means there is nopnpm-workspace.yamlin view at all.
Resolution and configuration patch
Widen or correct the globs:
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "packages/shared/*" # nested packages need their own pattern (or "packages/**")
- "!**/test/fixtures/**" # exclude fixture manifests from discovery
A ** pattern such as packages/** matches any depth, but it also matches fixture and example manifests inside packages, which then become workspace packages with confusing names. Prefer explicit single-level patterns plus exclusions.
Align the name:
{
"name": "@acme/ui",
"version": "1.9.0",
"private": true
}
Use range-less workspace specifiers unless you need to enforce a minimum local version. workspace:^ and workspace:* accept whatever version the local package has, and pnpm writes the concrete range at publish time:
{
"dependencies": {
"@acme/ui": "workspace:^"
}
}
Install from the root, or in Docker, copy the workspace definition before installing:
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
COPY packages/ui/package.json packages/ui/
COPY apps/web/package.json apps/web/
RUN pnpm install --frozen-lockfile --filter @acme/web...
For container builds of a single app, pnpm deploy or turbo prune produce a self-contained subset of the workspace; see Deploying a Single Package with pnpm deploy.
Worked example: a package moved during a refactor
A team reorganises its repository, moving shared React components from packages/ui to packages/design-system/ui so that tokens and icons can sit alongside. The move is a single git mv. The next install fails with WORKSPACE_PKG_NOT_FOUND for @acme/ui in three apps, and the "Packages found" line no longer lists it.
The glob packages/* matches packages/design-system, which has no package.json, and stops there. Adding packages/design-system/* to pnpm-workspace.yaml restores discovery, and the install succeeds. Two follow-ups make the fix durable: the lockfile is regenerated and committed in the same pull request (the importer path for @acme/ui changed, so the old entry is stale), and a CI check runs pnpm list -r --depth -1 --json and compares the package names against an expected list, so a future move that drops a package from discovery fails loudly with a clear message.
Publishing and the workspace protocol
The same discovery rules apply at publish time, with one extra twist. When pnpm packs a package, it replaces every workspace: range with a concrete version read from the local package it resolves to. If the local package cannot be found at that moment — for example, because a release job checked out only part of the repository or ran from a pruned subset — pnpm publish fails with the same not-found error rather than publishing a manifest with an unresolvable range. That behaviour is a safety net: the alternative is a published package whose dependencies read workspace:^, which every consumer's install rejects, as described in Fixing 'Unsupported URL Type workspace:' After Publishing.
Release tooling that publishes with npm publish instead of pnpm publish bypasses the rewrite entirely, because npm does not understand the protocol. If your pipeline uses Changesets, make sure it invokes pnpm for publishing (Changesets detects pnpm workspaces and does so by default), and never run npm publish inside a pnpm workspace package that has workspace: dependencies.
Other package managers
The same class of error exists everywhere the workspace protocol is supported. Yarn Berry reports YN0000: ... Workspace not found (@acme/ui@workspace:^) and discovers packages from the workspaces array in the root package.json. npm workspaces do not support the workspace: protocol at all; npm links local packages when a plain semver range matches the local version, and otherwise tries the registry — which turns a missing local package into an ETARGET or a 404 rather than a clear workspace error. Bun supports workspace: with discovery from the root manifest's workspaces field. If you move a repository between tools, re-check the discovery configuration first, because it lives in a different file for each.
CLI validation and debug commands
# List every package pnpm discovers, with paths
pnpm list -r --depth -1
# Machine-readable, for CI assertions
pnpm list -r --depth -1 --json | jq -r '.[].name' | sort
# Show the name and version pnpm reads for one folder
node -p "const p=require('./packages/design-system/ui/package.json'); p.name + '@' + p.version"
# Confirm the dependency now links to the local folder
ls -l apps/web/node_modules/@acme/ui
The symlink should point into the workspace (for example ../../../packages/design-system/ui), not into the .pnpm store.
Prevention and CI/CD guardrails
- Assert the discovered package list in CI so a move or rename that drops a package fails with an obvious diff.
- Prefer
workspace:^over versioned workspace ranges unless a minimum local version is truly required. - Keep folder names and package names aligned (
packages/uiholds@acme/ui) so discovery problems are easy to spot. - Exclude fixture manifests with negative globs so test data never becomes a workspace package.
Frequently Asked Questions
Why does pnpm not fall back to the registry for workspace: ranges? Because the protocol exists to guarantee a local link. Falling back silently would install a published version of a package you are editing locally, which is exactly the bug the protocol prevents. Use a plain semver range if you want registry fallback.
Do private packages need a version field to be found?
They need a name. A version is only required when the dependency uses a versioned range such as workspace:^1.0.0; workspace:* and workspace:^ work without one, although adding 0.0.0 keeps other tools happy.
Does the root package count as a workspace package?
Yes. The root package.json is always part of the workspace, so another package can depend on it by name with workspace:, although that is rarely a good idea.
Can a workspace package depend on another under a different alias?
Yes. "ui": "workspace:@acme/ui@*" installs the local @acme/ui under the name ui. The part after workspace: still has to match a discovered package name, so aliasing does not help with discovery problems.
Why does the error appear only in CI?
CI often builds from a sparse checkout, a Docker context or a pruned subset that lacks some package folders or the pnpm-workspace.yaml file. Compare the files present in the CI working directory with a full clone, and make sure the workspace definition is copied first.
Related
- Workspace Configuration Deep Dive explains discovery and linking across package managers.
- Using the workspace: Protocol Correctly covers the range variants and how they are published.
- Fixing pnpm --filter Matching No Projects is the filtering counterpart of this discovery problem.
- Migrating from Yarn 1 to pnpm Workspaces often surfaces this error when globs are translated.