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
- Pass overwrite: true to extract if replacing existing files is intended (e.g. entry.extract(dest, overwrite: true)).
- Extract into a fresh, unique temp directory (Dir.mktmpdir) per archive and move results afterwards.
- Delete or clear the destination before re-running a previously failed extraction.
- Detect collisions up front by checking File.exist? for each entry and renaming or skipping as appropriate.
- 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
- Extract into a fresh Dir.mktmpdir per archive
- Clean partial results after failed extractions
- Pass overwrite: true when replacement is intended
- Watch for name collisions on case-insensitive filesystems
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
- File not found
- Error identifying package type
- Invalid entry type
- Needs block or destination path
- no block given
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 << bufView on GitHub (pinned to 1c9f0bb801)