instructure/canvas-lms · warning · CanvasUnzip::DestinationFileExists

Destination '# ' already exists

Error message

Destination '#{dest_path}' already exists

What it means

CanvasUnzip::Entry#extract raises DestinationFileExists with this message when writing an archive entry to dest_path that already exists on disk and overwrite was not set to true. It protects against silently clobbering existing files during extraction.

Solutions

  1. Pass overwrite: true to extract if replacing existing files is intended (e.g. entry.extract(dest, overwrite: true)).
  2. Extract into a fresh, unique temp directory (Dir.mktmpdir) per archive and move results afterwards.
  3. Delete or clear the destination before re-running a previously failed extraction.
  4. Detect collisions up front by checking File.exist? for each entry and renaming or skipping as appropriate.
  5. Rescue CanvasUnzip::DestinationFileExists to handle per-file collisions gracefully in streaming mode.

Example fix

// before
entry.extract(dest_path) # raises if dest_path exists
// after
entry.extract(dest_path, overwrite: true)
# or
entry.extract(dest_path) unless File.exist?(dest_path)
Defensive patterns

Strategy: try-catch

Validate before calling

raise FileExists, dest_path if File.exist?(dest_path) && !overwrite

Type guard

def extraction_target_free?(dest_path)
  !File.exist?(dest_path)
end

Try / catch

begin
  entry.extract(dest_path)
rescue CanvasUnzip::DestinationFileExists
  entry.extract(dest_path, overwrite: true) if overwrite?
end

Prevention

When it happens

Trigger: Extracting an archive into a destination folder that already contains a file with the same name, without overwrite: true; extracting the same archive twice; entry names colliding after path sanitization (e.g. two entries normalizing to the same dest path).

Common situations: Re-running an import/extraction job that failed halfway (partial files left behind); extracting user uploads into a shared directory with fixed names; case-insensitive filesystems making 'A.txt' collide with 'a.txt'.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/4b46597665a891a0. Report an issue: GitHub.

Appendix: source

Thrown at gems/canvas_unzip/lib/canvas_unzip.rb:216

    def size
      case type
      when :zip
        entry.size
      when :tar
        entry.header.size
      end
    end

    # yields byte count
    def extract(dest_path, overwrite: false, maximum_size: DEFAULT_BYTE_LIMIT)
      dir = directory? ? dest_path : File.dirname(dest_path)
      FileUtils.mkdir_p(dir)
      return unless file?

      raise SizeLimitExceeded if size > maximum_size
      if File.exist?(dest_path) && !overwrite
        raise DestinationFileExists, "Destination '#{dest_path}' already exists"
      end

      digest = Digest::SHA2.new(512)
      ::File.open(dest_path, "wb") do |os|
        case type
        when :zip
          entry.get_input_stream do |is|
            entry.set_extra_attributes_on_path(dest_path)
            buf = +""
            while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
              os << buf
              digest.update(buf)
              yield(buf.size) if block_given?
            end
          end
        when :tar
          while (buf = entry.read(BUFFER_SIZE))
            os << buf

View on GitHub (pinned to 1c9f0bb801)