instructure/canvas-lms · warning

failed to reclaim attachment #

Error message

failed to reclaim attachment #{attachment.global_id}: #{attachment.errors.inspect}

What it means

Logged by the DataFixup::ReclaimInstfsAttachments fixup when an attachment previously stored in InstFS is re-uploaded locally (attachment.uploaded_data = attachment.open, instfs_uuid cleared) but the subsequent save fails ActiveRecord validations. The fixup logs the attachment's global_id plus attachment.errors and continues with the remaining attachments.

Solutions

  1. Read the attachment.errors in the log line to see which validation failed, and fix the underlying record (e.g. restore context, fix content_type).
  2. Verify the file exists and is retrievable from InstFS for the logged global_id; re-upload the missing object if the store lost it.
  3. Rerun the fixup after correcting the record so it gets reclaimed.
  4. If the file is unrecoverable, delete or mark the attachment (or skip it) so it stops failing on each run.

Example fix

// before (console inspection)
att = Attachment.find(attachment.global_id); pp att.errors
// after — fix the failing field, then retry
att.update!(context: Context.find(...)); DataFixup::ReclaimInstfsAttachments.run
Defensive patterns

Strategy: validation

Validate before calling

att = Attachment.find(global_id)
next unless att.file_state != 'deleted'
raise "source file missing" unless att.instfs_uuid.present? && att.open.present?
pp att.errors # inspect before attempting reclaim

Type guard

def reclaimable?(attachment)
  attachment.instfs_uuid.present? &&
    attachment.valid? &&
    attachment.context_type.present?
rescue StandardError
  false
end

Try / catch

begin
  attachment.uploaded_data = attachment.open
  attachment.instfs_uuid = nil
  attachment.save!
rescue ActiveRecord::RecordInvalid, OpenURI::HTTPError => e
  Rails.logger.warn("skipping #{attachment.global_id}: #{e.message}")
end

Prevention

When it happens

Trigger: attachment.save fails after re-uploading the file and clearing instfs_uuid — e.g. validation errors from invalid context/content_type, the source file being unreadable/empty during attachment.open, or quota/record-level validations failing on that Attachment row.

Common situations: Attachments whose file data is missing from InstFS (open raises or yields empty data), attachments belonging to soft-deleted or invalid contexts, corrupted rows failing presence/size validations, or S3/InstFS connectivity problems during the download step.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at lib/data_fixup/reclaim_instfs_attachments.rb:41

  # inst-fs enabled and had files uploaded to inst-fs need to stop using
  # inst-fs for whatever reasons (note this is beyond just stopping new uploads
  # to inst-fs).
  def self.run(root_accounts)
    Shard.partition_by_shard(root_accounts) do |shard_root_accounts|
      instfs_attachments_for_root_accounts(shard_root_accounts).find_each do |attachment|
        reclaim_attachment(attachment)
      end
    end
  end

  def self.reclaim_attachment(attachment)
    # NOTE: this downloads the whole attachment at once into a temp file.
    # unfortunately, this is unavoidable with how attachment_fu works
    attachment.uploaded_data = attachment.open
    attachment.instfs_uuid = nil
    unless attachment.save
      # continue with other attachments, but log this one for investigation
      Rails.logger.warn("failed to reclaim attachment #{attachment.global_id}: #{attachment.errors.inspect}")
    end
  end

  def self.instfs_attachments_for_root_accounts(root_accounts)
    # between all the subqueries below, we have all the enumerated context
    # types for an attachment from attachment.rb lines 51-59 except:
    #   * other attachments (recursive)
    #   * eportfolios
    #   * purgatory
    #   * users
    #   * specific instances of other context types (e.g. folders) that are
    #     connected to a user instead of to an account or course
    #
    # we have to punt on those because either:
    #   * unfolding the recursion would be prohibitive. there aren't many of
    #     these anyways
    #   * for the others, there's no way to connect the attachment to a
    #     specific account

View on GitHub (pinned to 1c9f0bb801)