pnpm/pnpm · error · AggregateError

Failed to clean up after global bin activation preparation f

Error message

Failed to clean up after global bin activation preparation failed.

What it means

prepareGlobalInstall backs up existing bin slots, materializes the fresh install, and reads the old hash-link target; if any preparation step throws, it removes backupDir and installDir as cleanup. When that cleanup itself fails, the preparation error plus cleanup errors are wrapped in an AggregateError ('Failed to clean up after global bin activation preparation failed.') whose `cause` is the original preparation error.

Source

Thrown at pnpm11/global/commands/src/globalActivation.ts:209

    await fs.promises.mkdir(opts.globalBinDir, { recursive: true })
    backupDir = await fs.promises.mkdtemp(path.join(opts.globalBinDir, '.pnpm-bin-backup-'))
    const savedBinSlots = await backupBinSlots({
      actualBinNames,
      backupDir,
      globalBinDir: opts.globalBinDir,
    })
    const oldHashTarget = await readHashTarget(opts.hashLink)
    return { actualBins, actualBinNames, backupDir, savedBinSlots, oldHashTarget }
  } catch (preparationError) {
    const cleanupResults = await Promise.allSettled([
      ...(backupDir == null ? [] : [fs.promises.rm(backupDir, { recursive: true, force: true })]),
      fs.promises.rm(opts.installDir, { recursive: true, force: true }),
    ])
    const cleanupErrors = cleanupResults.flatMap((result) => {
      return result.status === 'rejected' ? [result.reason] : []
    })
    if (cleanupErrors.length > 0) {
      throw new AggregateError(
        [preparationError, ...cleanupErrors],
        'Failed to clean up after global bin activation preparation failed.',
        { cause: preparationError }
      )
    }
    throw preparationError
  }
}

/** The commands the group declares, mapped to the file each one runs. */
async function getActualBins (opts: ActivateGlobalInstallOptions): Promise<Map<string, string>> {
  const actualBins = new Map<string, string>()
  const binsByPackage = await Promise.all(opts.pkgs.map(async ({ manifest, location }) => {
    return getBinsFromPackageManifest(manifest, location)
  }))
  for (const bins of binsByPackage) {
    for (const { name, path: binPath } of bins) {
      if (!opts.binsToSkip.has(name)) actualBins.set(name, binPath)

View on GitHub (pinned to 5b11d3a15b)

Solutions

  1. Unwrap `error.cause` (or errors[0]) and fix the underlying preparation failure first
  2. Manually remove the leftover installDir/backupDir if they still exist after the failure
  3. Fix permissions and locks on the global dirs, ensure free disk space, then re-run the command
Defensive patterns

Strategy: try-catch

Type guard

function isPrepCleanupAggregate (err: unknown): err is AggregateError & { cause: unknown } {
  return util.types.isNativeError(err) && Array.isArray((err as AggregateError).errors) && 'cause' in err
}

Try / catch

try {
  await activateGlobalInstall(opts)
} catch (err) {
  if (isPrepCleanupAggregate(err)) {
    // err.cause = the original preparation error; err.errors also contains cleanup failures
    // fix the cause first, then remove leftover install/backup dirs it reports
    handlePreparationFailure(err.cause ?? err.errors[0])
  }
  throw err
}

Prevention

When it happens

Trigger: A preparation step fails (e.g. backing up or reading a bin slot) AND the Promise.allSettled rm of backupDir/installDir rejects — locked or read-only files, permission problems, or disk full.

Common situations: Same Windows lock/permission conditions as other activation failures; disk exhaustion occurring mid-preparation; antivirus interfering with rapid create/remove cycles in PNPM_HOME.

Related errors


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