pnpm/pnpm · error · PnpmError
ERR_PNPM_GIT_CHECKOUT_FAILED
ERR_PNPM_GIT_CHECKOUT_FAILED
Error message
received commit ${receivedCommit.trim()} does not match expected value ${resolution.commit} What it means
After cloning (or shallow-fetching) and checking out resolution.commit, the git fetcher reads back HEAD via git rev-parse and requires an exact string match with the pinned commit. A mismatch means the working copy did not land on the pinned SHA — typically a shallow fetch that could not honor the exact commit, or upstream history rewritten between resolution and fetch.
Source
Thrown at pnpm11/fetching/git-fetcher/src/index.ts:49
throw new PnpmError('INVALID_GIT_COMMIT', `Invalid git commit hash "${resolution.commit}" for repository "${resolution.repo}". Expected a 40-character hexadecimal SHA.`)
}
const tempLocation = await cafs.tempDir()
try {
if (allowedHosts.size > 0 && shouldUseShallow(resolution.repo, allowedHosts)) {
await execGit(['init'], { cwd: tempLocation })
await execGit(['remote', 'add', 'origin', resolution.repo], { cwd: tempLocation })
await execGit(['fetch', '--depth', '1', 'origin', resolution.commit], { cwd: tempLocation })
} else {
await execGit(['clone', resolution.repo, tempLocation])
}
} catch (err: unknown) {
assert(util.types.isNativeError(err))
throw gitFetchError(err, resolution.repo, opts.pkg?.name)
}
await execGit(['checkout', resolution.commit], { cwd: tempLocation })
const receivedCommit = await execGit(['rev-parse', 'HEAD'], { cwd: tempLocation })
if (receivedCommit.trim() !== resolution.commit) {
throw new PnpmError('GIT_CHECKOUT_FAILED', `received commit ${receivedCommit.trim()} does not match expected value ${resolution.commit}`)
}
let pkgDir: string
try {
const prepareResult = await preparePackage({
allowBuild: opts.allowBuild,
ignoreScripts: createOpts.ignoreScripts,
pkgResolutionId: createGitHostedPkgId(resolution),
unsafePerm: createOpts.unsafePerm,
userAgent: createOpts.userAgent,
}, tempLocation, resolution.path ?? '')
pkgDir = prepareResult.pkgDir
if (ignoreScripts && prepareResult.shouldBeBuilt) {
globalWarn(`The git-hosted package fetched from "${resolution.repo}" has to be built but the build scripts were ignored.`)
}
} catch (err: unknown) {
assert(util.types.isNativeError(err))
err.message = `Failed to prepare git-hosted package fetched from "${resolution.repo}": ${err.message}`
throw errView on GitHub (pinned to 6261b7f388)
Solutions
- Re-run the install — if the mismatch was a transient race, a fresh resolve+fetch succeeds
- Update the pinned commit in the lockfile to the current upstream SHA (regenerate the lockfile)
- Remove the host from gitShallowHosts (config) so a full clone is used instead of a shallow fetch
Example fix
# before: pinned commit no longer reachable after upstream force-push $ pnpm install # ERR_PNPM_GIT_CHECKOUT_FAILED: received commit abc... does not match expected def... # after: re-resolve to the rewritten history $ rm pnpm-lock.yaml && pnpm install
Defensive patterns
Strategy: try-catch
Validate before calling
import { execa } from 'execa'
// Confirm the pinned commit is still the upstream tip-ish ref before fetching
async function assertCommitReachable (repo: string, commit: string): Promise<void> {
const { stdout } = await execa('git', ['ls-remote', repo])
const reachable = stdout.split('\n').some(line => line.startsWith(commit))
if (!reachable) {
throw new Error(`${commit} no longer advertised by ${repo}; history was probably rewritten — re-resolve`)
}
} Try / catch
try {
await fetchers.git(cafs, resolution, opts)
} catch (err) {
if (err instanceof PnpmError && err.code === 'ERR_PNPM_GIT_CHECKOUT_FAILED') {
// Either a force-push race (re-resolve and retry once) or shallow-fetch weirdness
const fresh = await reResolveGitDependency(resolution.repo)
if (fresh.commit === resolution.commit) throw err // deterministic — do not loop
return fetchers.git(cafs, { ...resolution, commit: fresh.commit }, opts)
}
throw err
} Prevention
- Prefer full clones over shallow fetches for git deps from hosts with unreliable commit fetching (drop them from gitShallowHosts)
- Re-lock git dependencies after upstream force-pushes instead of trusting old SHAs
- Retry once on mismatch — races between resolve and fetch are the common benign cause
When it happens
Trigger: The repo host is in gitShallowHosts and git fetch --depth 1 origin <commit> yields a different HEAD; the upstream force-pushed between resolve-time and fetch-time; a corrupted local git cache served the wrong commit.
Common situations: Racing an upstream force-push during CI; exotic git hosting with partial shallow-fetch support; rare flaky mirrors.
Related errors
- ERR_PNPM_INVALID_GIT_COMMIT
- ERR_PNPM_UNSUPPORTED_RESOLUTION_TYPE
- Unable to diff directories. Make sure you have a recent vers
- GIT_UNCLEAN
- GIT_UNKNOWN_BRANCH
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/a004ea9dfcc7d30e.
Report an issue: GitHub.