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
- 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).
- Verify the file exists and is retrievable from InstFS for the logged global_id; re-upload the missing object if the store lost it.
- Rerun the fixup after correcting the record so it gets reclaimed.
- 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
- Fix reported validation errors (attachment.errors) before rerunning the fixup
- Verify InstFS objects exist and are downloadable for the target attachment set
- Run the fixup per root account in small batches to isolate failing records
- Check context validity (not soft-deleted) for attachments before reclaiming
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
- att.upload_error_message
- Attachment verifier token expired: #
- Attachment verifier token id mismatch. token id: #
- Attachment verifier token invalid: #
- DataFixup::MoveFeatureFlagsToSettings => unable to handle…
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 accountView on GitHub (pinned to 1c9f0bb801)