Freika/dawarich · error · ArgumentError
zip has no entries
Error message
zip has no entries
What it means
Archive::Unzipper.extract_single opens the uploaded zip, rejects directory entries, and takes the first real file; if none exists it raises ArgumentError 'zip has no entries'. This runs on the single-entry path chosen during classification, so the archive passed the zip-integrity checks but contains only directory entries (or is empty). The import flow surfaces this as a user-facing import failure.
Source
Thrown at app/services/archive/unzipper.rb:47
return Result.new(kind: :not_a_zip)
end
return Result.new(kind: :not_a_zip) if entries.nil?
# Single-entry-with-unsupported-extension collapses to :multi_entry so
# the existing Imports::ZipExtractor handles the filtering rather than
# duplicating the supported-extensions list in two places.
if entries.size == 1 && supported_extension?(entries.first.name)
Result.new(kind: :single_entry, entry_name: entries.first.name)
else
Result.new(kind: :multi_entry)
end
end
def self.extract_single(path)
::Zip::File.open(path) do |zf|
entry = zf.entries.reject(&:directory?).first
raise ArgumentError, 'zip has no entries' unless entry
ext = File.extname(entry.name)
# Tempfile.create (not .new) returns a plain File without an
# ObjectSpace finalizer, so the path survives GC of the File object.
# The caller (Imports::Create) is responsible for unlinking.
inner = Tempfile.create(['unzipped', ext], binmode: true)
begin
bytes_written = 0
entry.get_input_stream do |stream|
while (chunk = stream.read(64 * 1024))
bytes_written += chunk.bytesize
if bytes_written > MAX_EXTRACTED_SIZE
inner.close
File.unlink(inner.path) if File.exist?(inner.path)
raise ArchiveTooLarge, "entry exceeds #{MAX_EXTRACTED_SIZE} bytes"
end
View on GitHub (pinned to 97fad417c5)
Solutions
- List the archive contents before importing: unzip -l file.zip - if only directories appear, that is the cause
- Re-create the zip so the GPX/CSV files sit at the archive root, not inside an empty folder chain
- Validate client-side (file count > 0, at least one supported extension) before uploading
- If building archives programmatically, skip directory entries so the zip contains only files
Example fix
# before
Zip::File.open('out.zip') { |zf| zf.mkdir('gpx') } # directory-only archive
# after
Zip::File.open('out.zip') do |zf|
zf.add('trace.gpx', 'trace.gpx') # at least one real file entry
end Defensive patterns
Strategy: validation
Validate before calling
require 'zip'
def zip_has_file_entries?(path)
Zip::File.open(path) { |zf| zf.entries.any? { |e| !e.directory? } }
rescue Zip::Error
false
end Try / catch
begin Archive::Unzipper.extract_single(path) rescue ArgumentError => e render_error(:unprocessable_entity, 'Archive contains no files - re-zip the files without an empty folder wrapper') end
Prevention
- Check entry counts client-side (JSZip or equivalent) before uploading
- Tell users to zip the files themselves, not a parent folder, in import documentation
- Treat directory-only archives as a user-input problem: fail with an actionable message instead of a generic import error
When it happens
Trigger: Uploading a zip that only contains folders (e.g. macOS Finder's 'Compress' on an empty folder, or zipping a directory whose files were filtered out), a zip whose single entry is a directory entry like 'gpx/', or an empty archive created by 'zip empty.zip' with no inputs.
Common situations: Users zipping a wrapper folder instead of its contents, automated export tools emitting directory-only archives, interrupted zip creation leaving structural entries but no files, nested zips where the outer layer was stripped and only folder markers remain.
Related errors
- entry exceeds #{MAX_EXTRACTED_SIZE} bytes
- Unknown export format: neither manifest.json nor data.json f
- Could not detect required columns: latitude, longitude, time
- Unsupported file format: %{format}
- GPX parse error: %{message}
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/98a8093982f7448c.
Report an issue: GitHub.