Back to monorepo orchestration Target affected workspaces Configure turbo pipelines Speed up type-checking

Fixing 'Unsupported URL Type workspace:' After Publishing

npm error code EUNSUPPORTEDPROTOCOL with Unsupported URL Type "workspace:" means someone installed a package whose published package.json still contains a workspace: range. The protocol only makes sense inside the monorepo that produced the package; on the registry it is an unresolvable string, so every consumer's install fails. The fix has two parts: publish a corrected version quickly, and change the release process so the protocol is always rewritten. The first part takes minutes; the second is what stops the same outage from recurring the next time someone publishes in a hurry. This guide covers both, plus how to detect the leak before it ships.

Exact symptoms and error messages

Consumers see the failure the moment they install or update:

npm error code EUNSUPPORTEDPROTOCOL
npm error Unsupported URL Type "workspace:": workspace:^

Yarn Classic reports:

error Couldn't find package "@acme/core@workspace:^" required by "@acme/react@1.8.0" on the "npm" registry.

pnpm, which understands the protocol, still fails because the referenced package is not in the consumer's workspace:

 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND  In : "@acme/core@workspace:^" is in the dependencies but no package named "@acme/core" is present in the workspace

Inspecting the published manifest confirms the leak:

npm view @acme/react@1.8.0 dependencies
# { '@acme/core': 'workspace:^', 'clsx': '^2.1.1' }

Root cause analysis

The workspace: protocol is rewritten into a normal semver range at pack time — but only by a package manager that understands it: pnpm, Yarn Berry and Bun. How the rewrite works is covered in Using the workspace: Protocol Correctly. The leak happens whenever something else creates the tarball:

How a workspace: range escapes into a published package The packing tool determines whether workspace ranges are rewritten; npm publish, custom scripts and some release tools skip the rewrite. Who packed the tarball? check the release job's publish command Rewritten workspace:^ becomes ^1.5.0 pnpm / yarn / bun Leaked npm copies package.json verbatim npm publish Leaked tar of the folder, or npm pack custom script
The protocol leaks whenever a tool that does not understand it creates the tarball.

The common routes to a leak:

  1. npm publish run inside a pnpm or Yarn workspace — by hand, or in a release script written before the repository adopted pnpm.
  2. A release tool configured to publish with npm. Changesets detects pnpm and Yarn workspaces and uses them, but custom publish commands or older tool versions may call npm directly.
  3. A CI action that publishes a directory with npm. Generic "publish to npm" actions often run npm publish in the package folder.
  4. A hand-built tarballtar of the folder, or npm pack followed by publishing the tarball.

Resolution: repair the registry first

Consumers are broken right now, so fix the published state before fixing the process.

Recovering from a leaked workspace range Publish a fixed patch with the right tool, deprecate the broken version, and move latest if needed, then fix the release process. pnpm publish a patch 1.8.1 with rewritten ranges same code, correct manifest npm deprecate 1.8.0 point users at 1.8.1 check dist-tags latest must point at 1.8.1 fix the release job publish only via pnpm prevents the next leak
You cannot republish over a broken version — ship a new patch and deprecate the old one.
  1. Publish a patch with the workspace-aware tool. Bump the version (you cannot overwrite 1.8.0) and publish with pnpm:
cd packages/react
pnpm version patch                 # 1.8.0 -> 1.8.1
pnpm publish --access public       # rewrites workspace:^ to ^<local version>
npm view @acme/react@1.8.1 dependencies   # verify: no workspace: strings
  1. Deprecate the broken version so anyone pinned to it gets a warning pointing to the fix:
npm deprecate @acme/react@1.8.0 "Broken dependency ranges; upgrade to 1.8.1"
  1. Check dist-tags. latest moves to 1.8.1 automatically when you publish it without a --tag. If you published the broken version under another tag such as next, move that tag too. Unpublishing is usually unnecessary and restricted by registry policy; deprecation is the standard remedy, covered in Deprecating npm Package Versions.

  2. Check every package from the same release. A release job that leaked in one package almost certainly leaked in all of them. List the published manifests of every package released together and repeat the fix where needed.

Fixing the release process

Make the workspace-aware package manager the only thing that ever packs or publishes:

{
  "scripts": {
    "release": "pnpm -r build && changeset publish"
  }
}

Changesets calls pnpm publish for each package when it detects a pnpm workspace. If you publish with a script, call pnpm publish (or pnpm -r publish), never npm publish:

- run: pnpm install --frozen-lockfile
- run: pnpm -r build
- run: pnpm -r publish --access public --no-git-checks
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

To make an accidental npm publish fail instead of leaking, add a guard in each publishable package:

{
  "scripts": {
    "prepublishOnly": "node -e \"if(!/^(pnpm|yarn)\\//.test(process.env.npm_config_user_agent||'')){console.error('Publish with pnpm, not npm.');process.exit(1)}\""
  }
}

Detecting a leak before it ships

Add a check to the release job that inspects each packed manifest:

for dir in packages/*; do
  (cd "$dir" && [ "$(node -p "require('./package.json').private||false")" = "true" ] && continue
   tarball=$(pnpm pack --pack-destination /tmp/packs | tail -1)
   if tar -xOzf "$tarball" package/package.json | grep -q '"workspace:'; then
     echo "workspace: range leaked in $dir"; exit 1
   fi)
done

Or rely on the tarball smoke test, which installs the packed packages together with npm and fails on any unresolvable range — see Smoke-Testing a Tarball with npm pack.

Catching the leak in the release job The release job packs each package with pnpm, inspects the packed manifest for workspace strings, and only publishes if none are found. Release job pnpm pack manifest check npm registry pack each public package tarball package.json no workspace: strings publish the checked tarballs
Inspecting the packed manifest costs seconds and turns a consumer-facing outage into a failed job.

Other protocols that leak the same way

workspace: is not the only repository-local specifier that can escape into a published manifest. The same class of bug happens with:

  • catalog: — pnpm catalog references, rewritten by pnpm at pack time into the catalog's version. Published with npm, they fail with the same EUNSUPPORTEDPROTOCOL error naming catalog:. The fix and prevention are identical, and pnpm catalogs are covered in Sharing Dependency Versions with pnpm Catalogs.
  • link: and portal: — local path protocols in pnpm and Yarn. They are never rewritten and should never appear in a published package's dependencies; if you need one during development, keep it in a private package.
  • file:../something — valid for npm and every package manager, but meaningless on a consumer's machine, where the relative path does not exist. The install fails with ENOENT or Could not install from "../something" as it does not contain a package.json file.
  • patch: — Yarn's patch protocol, which references a patch file in your repository. Yarn rewrites it to the base version when packing, but a hand-built tarball ships the reference.

A single release-job check that fails on any dependency range starting with workspace:, catalog:, link:, portal:, file: or patch: covers all of them:

tar -xOzf "$tarball" package/package.json \
  | jq -e '[.dependencies, .peerDependencies, .optionalDependencies] | map(select(. != null) | to_entries[]) | map(select(.value | test("^(workspace|catalog|link|portal|file|patch):"))) | length == 0'

Communicating with affected consumers

A leaked range breaks installs immediately, so consumers often find it before you do. Once the patch is out, a short, factual note in the issue tracker and the changelog — which version is broken, which version fixes it, and that the fix is a manifest-only change with identical code — resolves most reports. Consumers whose lockfiles captured the broken version can run their package manager's update command for your package; those who pinned it exactly need to change the pin. Because the broken version is deprecated, their installs also print your message, which points them to the fix without anyone having to find the issue.

Worked example: a hotfix published from a laptop

A maintainer needs to ship a one-line hotfix on a Friday evening. CI is slow, so they run npm version patch && npm publish inside packages/react from their laptop. Within twenty minutes, issue reports arrive: installs fail with EUNSUPPORTEDPROTOCOL. The maintainer publishes 1.8.2 with pnpm publish, deprecates 1.8.1, and posts a note on the issue. On Monday the team adds the prepublishOnly guard to every public package and restricts publish rights so that only the CI release job's token can publish, which also makes provenance possible, as described in Publishing from CI with npm Trusted Publishing.

Prevention and CI/CD guardrails

  • Publish only from CI, through the workspace-aware package manager.
  • Guard prepublishOnly against npm in every public package of a pnpm or Yarn workspace.
  • Inspect packed manifests for workspace: before publishing.
  • Keep npm out of the workspace with an install guard, as in Enforcing a Single Package Manager with only-allow.

Frequently Asked Questions

Can I fix the broken version without publishing a new one? No. Published versions are immutable. Publish a patch and deprecate the broken version.

Does npm plan to support the workspace protocol? npm workspaces link local packages without a protocol and do not rewrite workspace: strings. Treat the protocol as pnpm, Yarn and Bun specific.

Why did the leak only affect some consumers? Consumers whose lockfiles already pinned an earlier version were unaffected until they updated. Fresh installs and anyone updating to the broken version failed.

Should I unpublish the broken version instead of deprecating it? Usually not. Registry policy limits unpublishing, and removing a version breaks anyone whose lockfile references it even if their install happened to work through an override. Deprecation warns without breaking, as described in Unpublishing a Package Within npm Policy.

Can consumers work around it until the fix ships? Yes: npm overrides, pnpm overrides or Yarn resolutions can force the leaked dependency to a real version in the consumer's project. It is a temporary patch on their side; the publisher's fix is still needed.

Related

Cross-Package Dependency Management