{"record":{"id":"022d154c1c84866a","repo":"instructure/canvas-lms","slug":"no-essay-submission-found-after-removing-text-between","errorCode":null,"errorMessage":"No essay submission found after removing text between <>","messagePattern":"No essay submission found after removing text between <>","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/services/grade_service.rb","lineNumber":148,"sourceCode":"    end\n  end\n\n  def sanitize_essay(text)\n    # First decode any HTML entities\n    text = CGI.unescapeHTML(text)\n    text = ActionView::Base.full_sanitizer.sanitize(text)\n\n    # Remove any remaining HTML tags and their content\n    text = text.gsub(%r{<[^>]*>.*?</[^>]*>}, \"\")                          # Remove content between other opening and closing tags\n               .gsub(/<[^>]*>/, \"\")                                       # Remove any remaining opening tags\n               .gsub(%r{</[^>]*>}, \"\")                                    # Remove any remaining closing tags\n\n    # Remove any content between \\&lt; and \\&gt; (including the entities themselves)\n    text = text.gsub(%r{\\\\&lt;[^&]*\\\\&gt;.*?\\\\&lt;/[^&]*\\\\&gt;}, \"\")      # Remove content between encoded opening and closing tags\n               .gsub(/\\\\&lt;[^&]*\\\\&gt;/, \"\")                             # Remove any remaining encoded opening tags\n               .gsub(%r{\\\\&lt;/[^&]*\\\\&gt;}, \"\")                          # Remove any remaining encoded closing tags\n\n    raise \"No essay submission found after removing text between <>\" if text.blank?\n\n    # Remove lines starting with more than 3 # characters\n    text = text.split(\"\\n\").reject { |line| line.strip.start_with?(\"####\") }.join(\"\\n\")\n\n    # Clean up any resulting double spaces and trim\n    text.gsub(/\\s+/, \" \").strip\n  end\n\n  def validate_essay_length(text)\n    raise \"Submission must be at least 5 words long\" if text.split.size < 5\n  end\n\n  def friendly_cedar_error_for(error)\n    case error\n    when InstructureMiscPlugin::Extensions::CedarClient::CedarLimitReachedError\n      I18n.t(\"Grading is temporarily unavailable. Please try again later.\")\n    when InstructureMiscPlugin::Extensions::CedarClient::ContentTooLongError\n      I18n.t(\"The submission is too long to be graded automatically.\")","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/grade_service.rb#L130-L166","documentation":"GradeService#sanitize_essay strips encoded tag-like content (&lt;...&gt; and matching closing tags) from the submitted essay. If nothing remains after this stripping, there is no real essay text to grade, so the service raises instead of sending an empty submission to the Cedar grading engine. This catches essays composed almost entirely of markup or placeholder tags.","triggerScenarios":"An essay submission whose body consists only of HTML/tag-like content between angle brackets (or their HTML-encoded entities), which the gsub patterns remove entirely; submissions embedding only embedded-object tags (e.g. media embeds) with no prose; empty or whitespace-only submissions after tag removal.","commonSituations":"Students pasting rich-text embeds instead of writing text; submissions from tools that serialize content purely as tags; corrupted submissions where the text entry failed to save.","solutions":["Ensure the student submission contains actual prose outside of tags before invoking grading","Pre-check in the caller: text.gsub(tag patterns, '').blank? and show 'submission is empty' instead of calling GradeService","If legitimate text is being over-stripped, adjust the sanitize regexes so real content between &lt;/&gt; pairs is preserved"],"exampleFix":"// before\nresult = GradeService.new(essay: submission.body, rubric:).call\n// after\ncleaned = submission.body.gsub(%r{&lt;[^&]*&gt;}, '')\nraise I18n.t('submission is empty') if cleaned.strip.split.size < 5\nresult = GradeService.new(essay: submission.body, rubric:).call","handlingStrategy":"validation","validationCode":"stripped = essay.gsub(%r{&lt;[^&]*&gt;}, '').to_s\nraise 'empty after sanitization' if stripped.strip.blank?","typeGuard":"essay.present? && essay.gsub(/&lt;[^&]*&gt;/, '').strip.present?","tryCatchPattern":"begin\n  GradeService.new(essay:, rubric:, rubric_association:).call\nrescue RuntimeError => e\n  show_empty_submission_error if e.message.include?('No essay submission')\nend","preventionTips":["Require prose, not just embeds, in essay submissions","Check for blank body before invoking AI grading","Review sanitize regexes if real content gets stripped"],"tags":["grading","sanitization","empty-input"],"backgroundTag":"empty-required-field","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"}