{"record":{"id":"f2e9586f172a66ba","repo":"instructure/canvas-lms","slug":"file-not-found-canvas-unzip","errorCode":null,"errorMessage":"File not found","messagePattern":"File not found","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"gems/canvas_unzip/lib/canvas_unzip.rb","lineNumber":81,"sourceCode":"  end\n\n  # if a destination path is given, the archive will be extracted to that location\n  #   * files will be skipped if they already exist\n  # if no destination path is given, a block must be given,\n  #   * yields |entry, index| for each (safe) zip/tar entry available to be extracted\n  # returns a hash of lists of entries that were skipped by reason\n  #   { :unsafe => [list of entries],\n  #     :already_exists => [list of entries],\n  #     :filename_too_long => [list of entries],\n  #     :unknown_compression_method => [list of entries] }\n\n  def self.extract_archive(archive_filename, dest_folder = nil, limits: nil, nested_dir: nil)\n    warnings = {}\n    limits ||= default_limits(File.size(archive_filename))\n    bytes_left = limits.maximum_bytes\n    files_left = limits.maximum_files\n\n    raise ArgumentError, \"File not found\" unless File.exist?(archive_filename)\n    raise ArgumentError, \"Needs block or destination path\" unless dest_folder || block_given?\n\n    each_entry(archive_filename) do |entry, index|\n      if unsafe_entry?(entry)\n        add_warning(warnings, entry, :unsafe)\n        next\n      end\n\n      if block_given?\n        yield(entry, index)\n      else\n        raise FileLimitExceeded if files_left <= 0\n\n        begin\n          name = entry.name\n          name = name.sub(nested_dir, \"\") if nested_dir # pretend the dir doesn't exist\n          f_path = File.join(dest_folder, name)\n          entry.extract(f_path, maximum_size: bytes_left) do |size|","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_unzip/lib/canvas_unzip.rb#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nCanvasUnzip.extract_archive(attachment.filename, dest)\n// after\npath = attachment.full_filename\nraise \"missing archive #{path}\" unless File.exist?(path)\nCanvasUnzip.extract_archive(path, dest)","handlingStrategy":"validation","validationCode":"path = File.expand_path(archive_filename)\nraise Errno::ENOENT, path unless File.exist?(path)\nraise ArgumentError, \"not a file\" unless File.file?(path)","typeGuard":"def existing_archive?(path)\n  File.file?(path.to_s) && File.readable?(path)\nend","tryCatchPattern":"begin\n  CanvasUnzip.extract_archive(path, dest)\nrescue ArgumentError => e\n  raise e unless e.message == \"File not found\"\n  Rails.logger.error(\"archive missing: #{File.expand_path(path)}\")\n  raise MissingArchiveError, path\nend","preventionTips":["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"],"tags":["file","missing-file","argument-error","zip"],"backgroundTag":"file-not-found","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}