Keep `workspace:` Dependencies From Falling Back to the Registry
The pnpm workspace protocol makes local-package intent explicit. Check package names, workspace globs, and publish transforms when a monorepo resolves the wrong copy.
Local linking depends on identity and discovery
If an app unexpectedly uses a registry copy of an internal library, check three things: the package's name, the workspace glob in pnpm-workspace.yaml, and the dependency declaration. The package name must match exactly, and the directory must be included in the workspace.
Use an explicit workspace range when local resolution is required:
{
"dependencies": {
"@acme/ui": "workspace:*"
}
}Then list workspace packages and inspect the installed link. A folder that happens to live beside the app is not automatically a workspace package.
Understand what happens at publish time
The workspace: protocol is rewritten to an ordinary semver range when packages are packed for publication. Choose the range that consumers should receive and test the packed artifact if publication is part of the workflow. A package can work through a local symlink while omitting built files or declaring incorrect exports in its published form.
When a workspace package changes, rebuild its output before testing a consumer if the consumer imports dist rather than source. Keep build order explicit through scripts or task tooling. A local package that resolves correctly but has stale compiled output can look like a pnpm linking problem.
Finally inspect pnpm why @acme/ui and the lockfile importer entries. If both workspace and registry copies appear, trace the dependency ranges and aliases. Do not delete node_modules until you know whether the resolver selected the wrong source or the workspace package simply has not been built.
Verify the package boundary, not only the symlink
Print the discovered workspace package list and compare the package's declared name with the dependency key used by the consumer. Workspace globs are relative to the workspace root and can exclude nested package directories accidentally. A folder named ui is not necessarily package @acme/ui. If package names are scoped, punctuation and spelling must match exactly. Check for a second package with the same name, which can make local resolution confusing or invalid.
After installation, inspect the link target and use the package manager's dependency explanation command. Then inspect the package's exports, main, and types fields. A workspace link can be correct while the consumer imports a stale dist directory or a path that is missing from the packed artifact. If the package publishes compiled output, build it before running the consumer and make that order explicit in scripts or task orchestration.
Test what external consumers will receive
The workspace: protocol communicates local intent inside the monorepo and is transformed when a package is packed. Test packaging when publication is part of the product contract. Inspect the tarball file list and install it into a small temporary consumer project. This catches missing build files, undeclared runtime dependencies, and export-map mistakes that a local symlink can hide because the monorepo has access to source files that are not published.
If both a workspace and registry version are present, inspect aliases and version ranges to understand why. Do not “fix” the symptom by deleting the lockfile or all of node_modules; that can change the graph without identifying the declaration that selected the wrong copy. A useful regression test installs from a clean checkout, builds the workspace package, runs the consumer, and verifies the exact imported version or path. This proves the intended local package is part of the working graph.
For release preparation, inspect the packed manifest as well as the source manifest. A workspace range can be rewritten to a semver range during packing, and that range needs to admit the version that the release process will publish. Use a prerelease or staging registry when practical to test the consumer install before broad publication. This catches a mismatch between local workspace behavior and registry behavior while the release can still be corrected safely.
Also inspect the tarball file list and exports map. A local symlink may expose source files, tests, or generated declarations that are excluded from the published package. Install the packed archive into a temporary consumer and run the same import and type-check path used by downstream users. This is the meaningful proof that the workspace protocol and publish transform describe the same package contract.
If the consumer imports dist, build the workspace package first and make that dependency explicit. A correct link with stale compiled output can look like registry fallback. Check the lockfile importer and resolved link before deleting installation state, then repeat the clean-checkout test after a package rename or export-map change.