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
- Wait for the other Vagrant process to finish, then retry the command.
- Identify the holder: lsof / ps aux | grep vagrant - if none, the lock is stale.
- Remove the stale lock file at the path printed in the error (e.g. rm /path/to/box.lock) once no process holds it.
- 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
- Serialize box downloads in CI (one provision step) instead of racing parallel jobs.
- Set a per-job VAGRANT_HOME when jobs share a runner.
- After a hard kill of Vagrant, sweep stale *.lock files under ~/.vagrant.d before the next run.
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
- Ignoring provider config for validation...
- Vagrant attempted to acquire a lock named
- A command must be provided when the --elevated flag is…
- A Docker command executed by Vagrant didn't complete…
- A Docker command executed by Vagrant didn't complete…
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 JSONView on GitHub (pinned to 35f3160f4a)