{"record":{"id":"758226b37c2f9d6a","repo":"hashicorp/vagrant","slug":"download-to-global-vagrant-location-already-in-pro","errorCode":null,"errorMessage":"Download to global Vagrant location already in progress. This\nmay be caused by other Vagrant processes attempting to download\na file to the same location.\n\nDownload path: %{dest_path}\nLock file path: %{lock_file_path}","messagePattern":"Download to global Vagrant location already in progress\\. This\nmay be caused by other Vagrant processes attempting to download\na file to the same location\\.\n\nDownload path: %(.+?)\nLock file path: %(.+?)","errorType":"exception","errorClass":"Vagrant::Errors::DownloadAlreadyInProgress","httpStatus":null,"severity":"error","filePath":"lib/vagrant/action/builtin/box_add.rb","lineNumber":601,"sourceCode":"            if File.file?(d.destination)\n              env[:ui].info(I18n.t(\"vagrant.actions.box.download.resuming\"))\n            end\n          end\n\n          begin\n            mutex_path = d.destination + \".lock\"\n            Util::FileMutex.new(mutex_path).with_lock do\n              begin\n                d.download!\n              rescue Errors::DownloaderInterrupted\n                # The downloader was interrupted, so just return, because that\n                # means we were interrupted as well.\n                @download_interrupted = true\n                env[:ui].info(I18n.t(\"vagrant.actions.box.download.interrupted\"))\n              end\n            end\n          rescue Errors::VagrantLocked\n            raise Errors::DownloadAlreadyInProgress,\n              dest_path: d.destination,\n              lock_file_path: mutex_path\n          end\n\n          Pathname.new(d.destination)\n        end\n\n        # Tests whether the given URL points to a metadata file or a\n        # box file without completely downloading the file.\n        #\n        # @param [String] url\n        # @return [Boolean] true if metadata\n        def metadata_url?(url, env)\n          d = downloader(url, env, json: true, ui: false)\n          env[:hook].call(:authenticate_box_downloader, downloader: d)\n\n          # If we're downloading a file, cURL just returns no\n          # content-type (makes sense), so we just test if it is JSON","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/hashicorp/vagrant/blob/35f3160f4ad6edc3a9f3aa9570adfc1a4d73aaa5/lib/vagrant/action/builtin/box_add.rb#L583-L619","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: two shells run simultaneously\nvagrant box add ubuntu/jammy64   # -> DownloadAlreadyInProgress (lock held by other process)\n\n# after: wait for first to finish, or if no vagrant runs:\nps aux | grep '[v]agrant' || rm \"$(vagrant box list >/dev/null; echo $HOME/.vagrant.d/boxes/... )\"\n# simplest stale-lock cleanup (no vagrant processes running):\nfind ~/.vagrant.d -name '*.lock' -delete","handlingStrategy":"retry","validationCode":"lock = File.join(dest_dir, File.basename(dest) + \".lock\")\nif File.exist?(lock) && !process_holds_lock?(lock)\n  File.delete(lock)  # clear stale lock before vagrant runs\nend","typeGuard":"def lock_free?(lock_path)\n  !File.exist?(lock_path) || `fuser #{lock_path} 2>/dev/null`.empty?\nend","tryCatchPattern":"attempts = 0\nbegin\n  env.cli(\"box\", \"add\", name)\nrescue Vagrant::Errors::DownloadAlreadyInProgress => e\n  attempts += 1\n  # e.extra_data[:dest_path], [:lock_file_path]\n  retry if attempts < 5   # other process usually finishes soon\n  raise\nend","preventionTips":["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."],"tags":["vagrant","box-download","file-lock","concurrency","ci"],"backgroundTag":"file-lock-contention","analyzedSha":"35f3160f4ad6edc3a9f3aa9570adfc1a4d73aaa5","analyzedAt":"2026-08-21T13:34:32.514Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}