instructure/canvas-lms · error · ArgumentError
File not found
Error message
File not found
What it means
CanvasUnzip.extract_archive raises ArgumentError "File not found" when the archive_filename passed does not exist on disk (File.exist? fails). It is an upfront guard so the caller knows the path is wrong before any extraction logic runs.
Solutions
- Verify File.exist?(archive_filename) before calling, and log the resolved absolute path (File.expand_path).
- Use the attachment's persisted path (Attachment#full_filename / contextful file store) instead of the transient upload path.
- Ensure the extraction job runs on the node/storage where the file exists (shared volume, S3 download first).
- Fix path construction: join with the correct base directory and check for filename normalization issues.
Example fix
// before
CanvasUnzip.extract_archive(attachment.filename, dest)
// after
path = attachment.full_filename
raise "missing archive #{path}" unless File.exist?(path)
CanvasUnzip.extract_archive(path, dest) Defensive patterns
Strategy: validation
Validate before calling
path = File.expand_path(archive_filename) raise Errno::ENOENT, path unless File.exist?(path) raise ArgumentError, "not a file" unless File.file?(path)
Type guard
def existing_archive?(path) File.file?(path.to_s) && File.readable?(path) end
Try / catch
begin
CanvasUnzip.extract_archive(path, dest)
rescue ArgumentError => e
raise e unless e.message == "File not found"
Rails.logger.error("archive missing: #{File.expand_path(path)}")
raise MissingArchiveError, path
end Prevention
- Use persisted attachment paths, not transient upload names
- Log File.expand_path of archives in jobs
- Ensure extraction runs where the file storage is mounted
- Check file existence after upload persistence
When it happens
Trigger: Calling CanvasUnzip.extract_archive('/path/to/file.zip', dest) where the file was never written, was deleted, or the path contains a typo/relative-path base mismatch.
Common situations: Processing an upload whose temp file was already cleaned up; passing the original upload filename instead of the persisted attachment path; working directory differences making relative paths resolve elsewhere; delayed jobs running on a host without the shared file.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Destination '# ' already exists
- Needs block or destination path
- no block given
- no_migration_file
- block is required if module_name is not passed
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/f2e9586f172a66ba.
Report an issue: GitHub.
Appendix: source
Thrown at gems/canvas_unzip/lib/canvas_unzip.rb:81
end
# if a destination path is given, the archive will be extracted to that location
# * files will be skipped if they already exist
# if no destination path is given, a block must be given,
# * yields |entry, index| for each (safe) zip/tar entry available to be extracted
# returns a hash of lists of entries that were skipped by reason
# { :unsafe => [list of entries],
# :already_exists => [list of entries],
# :filename_too_long => [list of entries],
# :unknown_compression_method => [list of entries] }
def self.extract_archive(archive_filename, dest_folder = nil, limits: nil, nested_dir: nil)
warnings = {}
limits ||= default_limits(File.size(archive_filename))
bytes_left = limits.maximum_bytes
files_left = limits.maximum_files
raise ArgumentError, "File not found" unless File.exist?(archive_filename)
raise ArgumentError, "Needs block or destination path" unless dest_folder || block_given?
each_entry(archive_filename) do |entry, index|
if unsafe_entry?(entry)
add_warning(warnings, entry, :unsafe)
next
end
if block_given?
yield(entry, index)
else
raise FileLimitExceeded if files_left <= 0
begin
name = entry.name
name = name.sub(nested_dir, "") if nested_dir # pretend the dir doesn't exist
f_path = File.join(dest_folder, name)
entry.extract(f_path, maximum_size: bytes_left) do |size|View on GitHub (pinned to 1c9f0bb801)