instructure/canvas-lms · error · ContentUnavailable

No text available for file

Error message

No text available for file

What it means

After a supported, unlocked, size-validated attachment is fetched, resolve_file extracts its text (via extract_attachment_text inside a Rails.cache.fetch) and raises ContentUnavailable if the resulting text is blank. It means the file passed all access checks but yielded no analyzable text.

Solutions

  1. Open the attachment and confirm it actually contains extractable text (selectable text in a PDF viewer).
  2. Clear the Rails cache entry for text_cache_key_for(:file, shard_safe_key) if a bad extraction result was cached, then retry.
  3. OCR the file or convert it to a text-bearing format before attaching.
  4. If extraction should handle this content type, extend extract_attachment_text / supported_attachment? rather than masking the error.

Example fix

// before
# cached blank extraction from scanned PDF keeps raising
Rails.cache.delete(text_cache_key_for(:file, shard_safe_key))

// after
# fix the artifact: re-attach an OCR'd, text-bearing PDF
ocr_attachment = course.attachments.create(uploaded_data: ocr_pdf_path)
resolve_content(kind: :file, id: ocr_attachment.id)
Defensive patterns

Strategy: fallback

Validate before calling

# cannot be fully pre-validated; check the file has a text layer
# heuristic: warn if PDF is image-only or attachment is zero bytes
return render_error if attachment.size.to_i.zero?

Type guard

def extractable?(attachment)
  attachment.size.to_i.positive? && StudyAssist.new(course: attachment.context, user: User.new).send(:supported_attachment?, attachment)
end

Try / catch

begin
  content = service.resolve_content(kind: :file, id: file_id)
rescue StudyAssist::ContentUnavailable
  render json: { error: 'file_has_no_text' }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: resolve_content -> resolve_file where extract_attachment_text returns empty/whitespace for a valid, supported attachment — e.g. scanned image-only PDFs, empty text files, or an extractor returning nil for an unrecognized subtype.

Common situations: Image-only scanned PDFs with no OCR layer; a 'text/plain' attachment that is actually empty; extraction silently failing inside the cached block so a blank result gets cached and the error repeats until TEXT_CACHE_TTL expires.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

Thrown at app/services/study_assist.rb:182

      end

      Content.new(kind: :page, id: page.id, cache_key_with_version: shard_safe_key, text:)
    end

    def resolve_file(file_id)
      attachment = @course.attachments.find_by(id: file_id)
      raise ContentUnavailable, "File not found" if attachment.nil? || attachment.deleted?
      raise ContentUnavailable, "File access denied" unless attachment.grants_right?(@user, :read)
      raise ContentUnavailable, "File is locked" if attachment.locked_for?(@user, check_policies: true)
      raise UnsupportedContentType unless supported_attachment?(attachment)
      raise ContentTooLarge, "File exceeds #{MAX_FILE_BYTES} byte limit" if attachment.size && attachment.size > MAX_FILE_BYTES

      shard_safe_key = shard_safe_cache_key_for(attachment)
      text = Rails.cache.fetch(text_cache_key_for(:file, shard_safe_key), expires_in: TEXT_CACHE_TTL) do
        extract_attachment_text(attachment)
      end

      raise ContentUnavailable, "No text available for file" if text.blank?

      Content.new(kind: :file, id: attachment.id, cache_key_with_version: shard_safe_key, text:)
    end

    def supported_attachment?(attachment)
      return true if attachment.content_type&.start_with?("text/")

      ACCEPTED_FILE_MIMETYPES.include?(attachment.content_type)
    end

    def extract_attachment_text(attachment)
      return FileTextExtractionService.new(attachment:).call.text.to_s if EXTRACTOR_MIMETYPES.include?(attachment.content_type)

      raw = +""
      attachment.open { |chunk| raw << chunk }
      (attachment.content_type == "text/html") ? html_to_text(raw) : raw
    end

View on GitHub (pinned to 1c9f0bb801)