instructure/canvas-lms · error · ArgumentError

Zip File cannot have more than #

Error message

Zip File cannot have more than #{MAX_FILE_COUNT} entries

What it means

validate_against in lib/unzip_attachment.rb:279 raises ArgumentError when the uploaded zip contains more than MAX_FILE_COUNT entries. Canvas rejects the archive up front before extraction to prevent resource exhaustion from very large zips.

Solutions

  1. Split the zip into smaller archives each under MAX_FILE_COUNT entries
  2. Upload the files in batches instead of one large zip
  3. Increase MAX_FILE_COUNT if the deployment legitimately needs larger zips (with capacity review)
  4. Inspect the zip's entry count locally (unzip -l) before uploading

Example fix

// before
# course_export_with_10000_files.zip uploaded directly
// after
zip -r batch1.zip files_0001-0500/
zip -r batch2.zip files_0501-1000/ # each batch <= MAX_FILE_COUNT entries
Defensive patterns

Strategy: validation

Validate before calling

raise TooManyEntries if zip_entry_count(zip_path) > MAX_FILE_COUNT

Try / catch

begin
  UnzipAttachment.process(context, zip)
rescue ArgumentError => e
  render json: {error: e.message}, status: :bad_request
end

Prevention

When it happens

Trigger: Uploading a zip attachment to a course/folder whose entry count exceeds MAX_FILE_COUNT; calling UnzipAttachment with a zip containing thousands of small files.

Common situations: Instructors bulk-uploading course exports or LMS migration archives with many files; automated imports of content packages; students uploading zip assignments that explode into many entries.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at lib/unzip_attachment.rb:279

# quite seem appropriate to move it to it's own file
# since it's such an integral part of the unzipping
# process
class ZipFileStats
  MAX_FILE_COUNT = 250_000
  attr_reader :file_count, :total_size, :paths, :filename, :quota_remaining

  def initialize(filename)
    @filename = filename
    @paths = []
    @file_count = 0
    @total_size = 0
    @quota_remaining = nil
    process!
  end

  def validate_against(context)
    if file_count > MAX_FILE_COUNT
      raise ArgumentError, "Zip File cannot have more than #{MAX_FILE_COUNT} entries"
    end

    # check whether the nominal size of the zip's contents would exceed
    # quota, and reject the zip immediately if so
    quota_hash = Attachment.get_quota(context)
    if quota_hash[:quota] > 0
      if (quota_hash[:quota_used] + total_size) > quota_hash[:quota]
        raise Attachment::OverQuotaError, "Zip file would exceed quota limit"
      end

      @quota_remaining = quota_hash[:quota] - quota_hash[:quota_used]
    end
  end

  # since the central directory can lie, track quota during extraction as well
  # to prevent zip bomb denial-of-service attacks
  def charge_quota(size)
    return if @quota_remaining.nil?

View on GitHub (pinned to 1c9f0bb801)