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

The download was interrupted by an external signal. It did…

Error message

The download was interrupted by an external signal. It did not
complete.

What it means

Raised by Vagrant::Util::Downloader after a curl download when the Busy.busy interrupt callback fired (an external signal reached the process and set interrupted = true) — the download was cancelled by a signal, not by a curl failure. It is deliberately distinct from DownloaderError, which covers non-zero curl exits with a parsed error message.

Solutions

  1. If you cancelled deliberately, re-run the command — Vagrant restarts the download
  2. If the INT was unintended, find what sends SIGINT (signal-forwarding wrappers, timeout tools) and reconfigure or exclude the vagrant process
  3. For long downloads, use a stable link or mirror to shorten the interruption window
Defensive patterns

Strategy: retry

Try / catch

tries = 0
begin
  Vagrant::Util::Downloader.new(url, dest).download!
rescue Vagrant::Errors::DownloaderInterrupted
  tries += 1
  sleep 2
  retry if tries < 3
end

Prevention

When it happens

Trigger: Ctrl-C during `vagrant box add` or any box/config download; sending SIGINT to the Vagrant process; process supervisors or wrappers (dumb-init, CI cancellation) forwarding INT to the curl child mid-transfer.

Common situations: Users cancelling large box downloads; CI job cancellation races; init systems with aggressive signal forwarding.

Related errors


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

Appendix: source

Thrown at lib/vagrant/util/downloader.rb:210

      def execute_curl(options, subprocess_options, &data_proc)
        options = options.dup
        options.unshift("-q")
        options << subprocess_options

        # Create the callback that is called if we are interrupted
        interrupted  = false
        int_callback = Proc.new do
          @logger.info("Downloader interrupted!")
          interrupted = true
        end

        # Execute!
        result = Busy.busy(int_callback) do
          Subprocess.execute("curl", *options, &data_proc)
        end

        # If the download was interrupted, then raise a specific error
        raise Errors::DownloaderInterrupted if interrupted

        # If it didn't exit successfully, we need to parse the data and
        # show an error message.
        if result.exit_code != 0
          @logger.warn("Downloader exit code: #{result.exit_code}")
          check = result.stderr.match(/\n*curl:\s+\((?<code>\d+)\)\s*(?<error>.*)$/)
          if check && check[:code] == "416"
            # All good actually. 416 means there is no more bytes to download
            @logger.warn("Downloader got a 416, but is likely fine. Continuing on...")
          else
            if !check
              err_msg = result.stderr
            else
              err_msg = check[:error]
            end

            raise Errors::DownloaderError,
              code: result.exit_code,

View on GitHub (pinned to 35f3160f4a)