instructure/canvas-lms · error
Empty file
Error message
Empty file
What it means
During SIS CSV parallel import, try_importing_segment opens the parallel importer's attachment and stats the file before processing. A zero-byte file means there is no CSV data to process, so the segment is rejected with 'Empty file' before importer_object.process runs.
Solutions
- Check which CSV attachment is empty and regenerate/re-upload it with actual data
- Fix the upstream export to skip (not emit) data types with no rows
- Add a pre-flight size check on the SIS zip contents before submitting the batch
- Ensure the upload completed (compare byte sizes/checksums against source) before enqueueing
Example fix
// before
SisBatch.submit_with_files(files: [empty_csv])
// after
non_empty = files.select { |f| File.size(f) > 0 }
raise 'no CSV data' if non_empty.empty?
SisBatch.submit_with_files(files: non_empty) Defensive patterns
Strategy: validation
Validate before calling
raise 'empty SIS csv' unless parallel_importer.attachment && parallel_importer.attachment.size > 0
Try / catch
begin
batch.run_parallel_importers
rescue RuntimeError => e
raise unless e.message == 'Empty file'
batch.mark_errored('a SIS CSV segment was empty')
end Prevention
- Reject zero-byte files at SIS upload time
- Make upstream exports omit empty data types
- Verify file sizes/checksums after SFTP transfer
When it happens
Trigger: An SIS batch was submitted where one of the split parallel-import CSV attachments is 0 bytes — e.g. the upload was truncated, an empty file was attached for a data type, or the SIS integration generated a header-less/empty export for that segment.
Common situations: Upstream SIS export jobs producing empty files on nights with no data changes; users uploading placeholder/empty CSVs via the SIS import UI; SFTP drops with partial files.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- No type given for change_sis_id
- A cross-listing referenced a non-existent section #
- A deleted cross-listing failed: #
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/2dcb0baf689e5e82.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/csv/import_refactored.rb:292
csv ||= { file: parallel_importer.attachment.display_name }
fail_with_error!(e, csv:)
end
ensure
if !(@run_immediately || ensure_later) &&
is_last_parallel_importer_of_type?(parallel_importer) &&
!should_stop_import?
queue_next_importer_set
end
end
def try_importing_segment(input_csv, parallel_importer, importer_object, skip_progress: false)
csv = input_csv || begin
att = parallel_importer.attachment
file = att.open(integrity_check: true)
{ fullpath: file.path, file: att.display_name }
end
parallel_importer.start
raise "Empty file" if File.stat(csv[:fullpath]).size == 0
count = importer_object.process(csv, parallel_importer.index, parallel_importer.batch_size)
parallel_importer.complete(rows_processed: count)
# just update progress on completion - the parallel jobs should be short enough
update_progress unless skip_progress
ensure
file&.close
end
def fail_with_error!(e, csv: nil)
return @batch if @batch.workflow_state == "aborted"
message = "Importing CSV for account: " \
"#{@root_account.id} (#{@root_account.name}) sis_batch_id: #{@batch.id}: #{e}"
err_id = Canvas::Errors.capture(e, {
type: :sis_import,
message:,
during_tests: falseView on GitHub (pinned to 1c9f0bb801)