750 words
4 minutes

pnpm Git Dependencies on CI: Stop SSH URLs from Breaking Installs

2026-08-08
DevOps
DevOps
/
CLI
/
Troubleshooting
/
Migration

An install that works on a developer laptop can fail on CI when a Git dependency resolves through [email protected], but the runner has no SSH key. The pnpm 12.0.0-rc.1 release changes the resolution boundary for GitHub, GitLab, and Bitbucket dependencies: known-host Git identities are canonicalized to HTTPS, and the lockfile no longer records an SSH URL for those hosts.

That is a pre-release behavior, so do not upgrade a production repository to the release candidate without a compatibility test. The useful troubleshooting rule is stable either way: make the repository URL, CI credential method, and lockfile policy agree instead of relying on one developer’s Git configuration.

What pnpm 12 RC changes#

The pnpm release candidate treats Git dependencies from known hosts as repository identities rather than preserving every URL spelling. When an anonymous archive is available, pnpm can use it; otherwise it clones the canonical HTTPS URL. Private access still requires credentials.

The change is intentionally narrower than “rewrite every Git URL”:

  • GitHub, GitLab, and Bitbucket URLs receive the known-host behavior.
  • Unknown self-hosted hosts keep their existing handling.
  • Credential-bearing URLs are not normalized away.
  • A canonical HTTPS URL does not make a private repository public or remove its authentication requirement.

This helps prevent a machine-specific SSH URL from entering a lockfile that later runs on a runner with HTTPS credentials only. It does not solve missing repository permissions, expired tokens, or a private repository that the runner cannot reach.

Find the URL that CI is actually using#

Start with the dependency specification and the lockfile rather than changing Git globally:

Terminal window
rg -n 'git\+ssh|git@github\.com|git\+https|github:|gitlab:|bitbucket:' \
package.json pnpm-lock.yaml

Then reproduce the access method outside pnpm with a harmless metadata request. Use a test repository or a repository the runner is authorized to read, and do not print tokens in CI logs:

Terminal window
git ls-remote https://github.com/example/private-repo.git refs/heads/main

If this fails, changing the package manager cannot repair the runner’s credentials. Configure the CI provider’s short-lived token or SSH key first, then verify the same URL with the same identity the install job uses.

Keep private Git access on SSH when the runner needs it#

The pnpm release notes document a machine-level Git rewrite for private hosted repositories that must still use SSH:

Terminal window
git config --global url."[email protected]:".insteadOf https://github.com/

This lets a canonical HTTPS dependency be fetched through the runner’s SSH transport. It belongs in the machine or CI image configuration, not in a project file that silently changes every contributor’s Git behavior. Restrict the key to read-only repository access and confirm that the runner’s known_hosts policy is managed as part of the image or CI service.

Inspect the effective rewrite before running the full install:

Terminal window
git config --global --get-regexp '^url\..*\.insteadOf$'
git ls-remote https://github.com/example/private-repo.git refs/heads/main

If your CI platform already provides HTTPS credentials, do not add an SSH rewrite just because a local machine uses one. The simpler path is usually to keep HTTPS and configure the platform’s credential helper or token injection according to its secret-handling rules.

Make the lockfile the CI contract#

Once the dependency can be fetched by the intended identity, make CI fail when pnpm would need to rewrite the lockfile:

Terminal window
pnpm install --frozen-lockfile

--frozen-lockfile keeps the install from silently changing the lockfile. This is useful for both the release candidate and stable pnpm versions because it exposes an out-of-sync dependency graph as a build failure instead of creating an unreviewed lockfile mutation on the runner.

When testing pnpm 12 RC, pin the exact version in an isolated job and compare the resulting install and lockfile with the version used by production. If the project is not intentionally testing the RC, keep its existing pnpm version and use the URL and credential checks above to diagnose the failure.

The Dockerized Astro static-site guide shows the same broader principle in a build image: copy package metadata first, install with the lockfile contract, and keep the build environment reproducible.

FAQ#

Does pnpm’s canonical HTTPS URL expose a private repository?#

No. HTTPS is a transport and URL form, not an access grant. A private repository still requires a token, credential helper, or an SSH rewrite that the runner is authorized to use.

Should every project upgrade to pnpm 12.0.0-rc.1?#

No. It is a release candidate. Pin it only in a deliberate test or migration job, compare the lockfile and install behavior, and keep production on the version your project has validated until the upgrade is approved.

Why does the fix work for GitHub but not my self-hosted Git server?#

The release describes special handling for known hosted services. Unknown self-hosted hosts keep their existing behavior, so configure that host’s credentials and URL policy explicitly rather than assuming the same normalization rules apply.

References:

pnpm 12.0.0-rc.1 release notes

pnpm install documentation

Git URL rewriting with insteadOf

pnpm Git Dependencies on CI: Stop SSH URLs from Breaking Installs
https://laplusda.com/en/posts/pnpm-git-dependencies-ssh-ci/
Author
Zero
Published at
2026-08-08
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.