pnpm/pnpm · error · PnpmError

BROKEN_LOCKFILE

BROKEN_LOCKFILE

Error message

The lockfile at "${opts.lockfileDir}/${WANTED_LOCKFILE}" is broken: it is empty

What it means

Thrown by readLockfiles when a frozen install (--frozen-lockfile, the CI default) finds that pnpm-lock.yaml exists on disk but parsed to null, typically a zero-byte file. A normal install would silently regenerate the file, but a frozen install promises not to modify the lockfile, so an unusable wanted lockfile is fatal.

Source

Thrown at pnpm11/installing/context/src/readLockfiles.ts:113

    }
    fileReads.push(Promise.resolve(undefined))
  }
  fileReads.push(
    (async () => {
      try {
        return await readCurrentLockfile(opts.internalPnpmDir, lockfileOpts)
      } catch (err: any) { // eslint-disable-line
        logger.warn({
          message: `Ignoring broken lockfile at ${opts.internalPnpmDir}: ${err.message as string}`,
          prefix: opts.lockfileDir,
        })
        return undefined
      }
    })()
  )
  const files = await Promise.all<LockfileObject | null | undefined>(fileReads)
  if (opts.frozenLockfile && wantedLockfileFileExists && files[0] == null) {
    throw new PnpmError('BROKEN_LOCKFILE', `The lockfile at "${path.join(opts.lockfileDir, WANTED_LOCKFILE)}" is broken: it is empty`)
  }
  const sopts = {
    autoInstallPeers: opts.autoInstallPeers,
    excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
    lockfileVersion: wantedLockfileVersion,
    peersSuffixMaxLength: opts.peersSuffixMaxLength,
  }
  const importerIds = opts.projects.map((importer) => importer.id)
  const currentLockfile = files[1] ?? createLockfileObject(importerIds, sopts)
  for (const importerId of importerIds) {
    if (!currentLockfile.importers[importerId]) {
      currentLockfile.importers[importerId] = {
        specifiers: {},
      }
    }
  }
  const existsWantedLockfile = files[0] != null
  const existsCurrentLockfile = files[1] != null

View on GitHub (pinned to 5b11d3a15b)

Solutions

  1. Run `pnpm install --no-frozen-lockfile` (or plain `pnpm install` locally) to regenerate pnpm-lock.yaml and commit it
  2. Check the file is not empty: `wc -c pnpm-lock.yaml`
  3. If a merge is in progress, resolve the lockfile conflict and run `pnpm install` before pushing
  4. Verify pnpm-lock.yaml is not listed in .gitignore and was actually pushed

Example fix

# before: CI frozen install fails, pnpm-lock.yaml is 0 bytes
cat pnpm-lock.yaml   # (empty)

# after: regenerate and commit a real lockfile
pnpm install --no-frozen-lockfile
git add pnpm-lock.yaml && git commit -m 'chore: restore pnpm-lock.yaml'
Defensive patterns

Strategy: validation

Validate before calling

// CI pre-flight: refuse a frozen install when the lockfile is empty
import { readFileSync } from 'fs'
import { join } from 'path'

export function lockfileIsReadable (lockfileDir: string): boolean {
  try {
    const content = readFileSync(join(lockfileDir, 'pnpm-lock.yaml'), 'utf8').trim()
    return content.length > 0 && content.includes('lockfileVersion:')
  } catch {
    return false
  }
}
// if (!lockfileIsReadable(lockfileDir)) throw new Error('pnpm-lock.yaml is missing or empty')

Type guard

import util from 'util'
import { PnpmError } from '@pnpm/error'

export const isBrokenLockfileError = (err: unknown): err is PnpmError =>
  util.types.isNativeError(err) && (err as PnpmError).code === 'BROKEN_LOCKFILE'

Try / catch

try {
  await run('pnpm', ['install', '--frozen-lockfile'])
} catch (err) {
  if (isBrokenLockfileError(err)) {
    // regenerate the lockfile, then retry without frozen
    await run('pnpm', ['install', '--no-frozen-lockfile'])
  } else {
    throw err
  }
}

Prevention

When it happens

Trigger: Running `pnpm install --frozen-lockfile` (or any install in CI, where frozenLockfile defaults to true) while <lockfileDir>/pnpm-lock.yaml exists but is empty or parses to null, so files[0] (the wanted lockfile) is null and wantedLockfileFileExists is true.

Common situations: Lockfile truncated by an interrupted git operation or a resolved-then-emptied merge conflict; a disk-full write; an empty pnpm-lock.yaml accidentally committed and then checked out in CI.

Related errors


AI-assisted analysis of pnpm/pnpm@5b11d3a15b (2026-08-16). Data as JSON: /api/errors/a96a4dd8eb1b1072. Report an issue: GitHub.