{"record":{"id":"7799e9e952126acc","repo":"instructure/canvas-lms","slug":"no-text-available-for-file","errorCode":null,"errorMessage":"No text available for file","messagePattern":"No text available for file","errorType":"validation","errorClass":"ContentUnavailable","httpStatus":null,"severity":"error","filePath":"app/services/study_assist.rb","lineNumber":182,"sourceCode":"      end\n\n      Content.new(kind: :page, id: page.id, cache_key_with_version: shard_safe_key, text:)\n    end\n\n    def resolve_file(file_id)\n      attachment = @course.attachments.find_by(id: file_id)\n      raise ContentUnavailable, \"File not found\" if attachment.nil? || attachment.deleted?\n      raise ContentUnavailable, \"File access denied\" unless attachment.grants_right?(@user, :read)\n      raise ContentUnavailable, \"File is locked\" if attachment.locked_for?(@user, check_policies: true)\n      raise UnsupportedContentType unless supported_attachment?(attachment)\n      raise ContentTooLarge, \"File exceeds #{MAX_FILE_BYTES} byte limit\" if attachment.size && attachment.size > MAX_FILE_BYTES\n\n      shard_safe_key = shard_safe_cache_key_for(attachment)\n      text = Rails.cache.fetch(text_cache_key_for(:file, shard_safe_key), expires_in: TEXT_CACHE_TTL) do\n        extract_attachment_text(attachment)\n      end\n\n      raise ContentUnavailable, \"No text available for file\" if text.blank?\n\n      Content.new(kind: :file, id: attachment.id, cache_key_with_version: shard_safe_key, text:)\n    end\n\n    def supported_attachment?(attachment)\n      return true if attachment.content_type&.start_with?(\"text/\")\n\n      ACCEPTED_FILE_MIMETYPES.include?(attachment.content_type)\n    end\n\n    def extract_attachment_text(attachment)\n      return FileTextExtractionService.new(attachment:).call.text.to_s if EXTRACTOR_MIMETYPES.include?(attachment.content_type)\n\n      raw = +\"\"\n      attachment.open { |chunk| raw << chunk }\n      (attachment.content_type == \"text/html\") ? html_to_text(raw) : raw\n    end\n","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/study_assist.rb#L164-L200","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Open the attachment and confirm it actually contains extractable text (selectable text in a PDF viewer).","Clear the Rails cache entry for text_cache_key_for(:file, shard_safe_key) if a bad extraction result was cached, then retry.","OCR the file or convert it to a text-bearing format before attaching.","If extraction should handle this content type, extend extract_attachment_text / supported_attachment? rather than masking the error."],"exampleFix":"// before\n# cached blank extraction from scanned PDF keeps raising\nRails.cache.delete(text_cache_key_for(:file, shard_safe_key))\n\n// after\n# fix the artifact: re-attach an OCR'd, text-bearing PDF\nocr_attachment = course.attachments.create(uploaded_data: ocr_pdf_path)\nresolve_content(kind: :file, id: ocr_attachment.id)","handlingStrategy":"fallback","validationCode":"# cannot be fully pre-validated; check the file has a text layer\n# heuristic: warn if PDF is image-only or attachment is zero bytes\nreturn render_error if attachment.size.to_i.zero?","typeGuard":"def extractable?(attachment)\n  attachment.size.to_i.positive? && StudyAssist.new(course: attachment.context, user: User.new).send(:supported_attachment?, attachment)\nend","tryCatchPattern":"begin\n  content = service.resolve_content(kind: :file, id: file_id)\nrescue StudyAssist::ContentUnavailable\n  render json: { error: 'file_has_no_text' }, status: :unprocessable_entity\nend","preventionTips":["Ensure attached documents contain a real text layer (OCR scanned PDFs before upload)","Avoid attaching empty text files","Clear stale cache entries for the file's text cache key after re-uploading a fixed file"],"tags":["text-extraction","attachments","cache"],"backgroundTag":"empty-result-set","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"}