hashicorp/vagrant · error · Vagrant::Errors::DownloadAlreadyInProgress

Download to global Vagrant location already in progress…

Error message

Download to global Vagrant location already in progress. This
may be caused by other Vagrant processes attempting to download
a file to the same location.

Download path: %{dest_path}
Lock file path: %{lock_file_path}

What it means

Errors::DownloadAlreadyInProgress wraps Errors::VagrantLocked from Util::FileMutex: the destination file in the global Vagrant box directory is already locked by another download. Vagrant serializes writes to ~/.vagrant.d via a '<dest>.lock' file, and contention aborts the second process.

Solutions

  1. Wait for the other Vagrant process to finish, then retry the command.
  2. Identify the holder: lsof / ps aux | grep vagrant - if none, the lock is stale.
  3. Remove the stale lock file at the path printed in the error (e.g. rm /path/to/box.lock) once no process holds it.
  4. In CI, serialize box downloads or give each job an isolated VAGRANT_HOME.

Example fix

# before: two shells run simultaneously
vagrant box add ubuntu/jammy64   # -> DownloadAlreadyInProgress (lock held by other process)

# after: wait for first to finish, or if no vagrant runs:
ps aux | grep '[v]agrant' || rm "$(vagrant box list >/dev/null; echo $HOME/.vagrant.d/boxes/... )"
# simplest stale-lock cleanup (no vagrant processes running):
find ~/.vagrant.d -name '*.lock' -delete
Defensive patterns

Strategy: retry

Validate before calling

lock = File.join(dest_dir, File.basename(dest) + ".lock")
if File.exist?(lock) && !process_holds_lock?(lock)
  File.delete(lock)  # clear stale lock before vagrant runs
end

Type guard

def lock_free?(lock_path)
  !File.exist?(lock_path) || `fuser #{lock_path} 2>/dev/null`.empty?
end

Try / catch

attempts = 0
begin
  env.cli("box", "add", name)
rescue Vagrant::Errors::DownloadAlreadyInProgress => e
  attempts += 1
  # e.extra_data[:dest_path], [:lock_file_path]
  retry if attempts < 5   # other process usually finishes soon
  raise
end

Prevention

When it happens

Trigger: Two vagrant box add / vagrant up (auto box download) processes race for the same box file; Util::FileMutex#with_lock on d.destination + '.lock' raises Errors::VagrantLocked, which is rescued and re-raised as DownloadAlreadyInProgress with dest_path and lock_file_path.

Common situations: Parallel CI jobs sharing a cached ~/.vagrant.d; running 'vagrant up' on multiple machines/projects simultaneously; a crashed previous run left a stale .lock file behind; concurrent plugin-driven downloads.

Related errors


AI-assisted analysis of hashicorp/vagrant@35f3160f4a (2026-08-21). Data as JSON: /api/errors/758226b37c2f9d6a. Report an issue: GitHub.

Appendix: source

Thrown at lib/vagrant/action/builtin/box_add.rb:601

            if File.file?(d.destination)
              env[:ui].info(I18n.t("vagrant.actions.box.download.resuming"))
            end
          end

          begin
            mutex_path = d.destination + ".lock"
            Util::FileMutex.new(mutex_path).with_lock do
              begin
                d.download!
              rescue Errors::DownloaderInterrupted
                # The downloader was interrupted, so just return, because that
                # means we were interrupted as well.
                @download_interrupted = true
                env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted"))
              end
            end
          rescue Errors::VagrantLocked
            raise Errors::DownloadAlreadyInProgress,
              dest_path: d.destination,
              lock_file_path: mutex_path
          end

          Pathname.new(d.destination)
        end

        # Tests whether the given URL points to a metadata file or a
        # box file without completely downloading the file.
        #
        # @param [String] url
        # @return [Boolean] true if metadata
        def metadata_url?(url, env)
          d = downloader(url, env, json: true, ui: false)
          env[:hook].call(:authenticate_box_downloader, downloader: d)

          # If we're downloading a file, cURL just returns no
          # content-type (makes sense), so we just test if it is JSON

View on GitHub (pinned to 35f3160f4a)