{"record":{"id":"88dc890a2042cb3b","repo":"instructure/canvas-lms","slug":"needs-block-or-destination-path","errorCode":null,"errorMessage":"Needs block or destination path","messagePattern":"Needs block or destination path","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"gems/canvas_unzip/lib/canvas_unzip.rb","lineNumber":82,"sourceCode":"\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|\n            bytes_left -= size","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_unzip/lib/canvas_unzip.rb#L64-L100","documentation":"CanvasUnzip.extract_archive requires either a destination folder path or a block; it raises ArgumentError \"Needs block or destination path\" when called with neither (dest_folder nil and no block_given?). The API supports two modes — write to disk or stream entries to a block — and this call matches neither.","triggerScenarios":"CanvasUnzip.extract_archive('x.zip') with no second argument and no block; calling with dest_folder: nil explicitly (e.g. variable that failed to initialize) in a keyword-args-capable call site.","commonSituations":"Refactoring from block style to path style and removing both; a dest variable derived from settings that is blank; copy-paste of each_entry-style calls into extract_archive.","solutions":["Pass a destination directory string: CanvasUnzip.extract_archive(path, '/tmp/extracted').","Or supply a block: CanvasUnzip.extract_archive(path) { |entry, i| ... }.","Fix the code path that left dest_folder nil (check ENV/setting lookup defaults).","If both modes are conditional, add an explicit error/validation in your own code before calling."],"exampleFix":"// before\nCanvasUnzip.extract_archive(zip_path)\n// after\nCanvasUnzip.extract_archive(zip_path, Rails.root.join('tmp/uploads/extracted').to_s)","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"need dest_folder or block\" if dest_folder.blank? && !block_given?","typeGuard":"def extraction_args_valid?(dest_folder)\n  !dest_folder.nil? && !dest_folder.to_s.empty?\nend","tryCatchPattern":"begin\n  CanvasUnzip.extract_archive(path, dest)\nrescue ArgumentError => e\n  raise e unless e.message == \"Needs block or destination path\"\n  raise UsageError, \"extract_archive requires dest_folder or a block\"\nend","preventionTips":["Pick one extraction mode (path or block) per call site","Validate settings that supply dest paths with defaults","Avoid passing possibly-nil dest variables","Add call-site guards when wrapping the API"],"tags":["missing-required-argument","argument-error","zip","api-misuse"],"backgroundTag":"missing-required-argument","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"}