{"record":{"id":"d6e5c2357dcbca88","repo":"instructure/canvas-lms","slug":"invalid-attachment","errorCode":null,"errorMessage":"Invalid Attachment","messagePattern":"Invalid Attachment","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/abstract_assignment.rb","lineNumber":2752,"sourceCode":"  ].freeze\n  ALLOWABLE_SUBMIT_HOMEWORK_OPTS = (SUBMIT_HOMEWORK_ATTRS +\n                                    %w[comment group_comment attachments require_submission_type_is_valid resource_link_lookup_uuid student_id]).to_set\n\n  def submit_homework(original_student, opts = {})\n    raise \"Student Required\" unless original_student\n\n    eula_timestamp = opts[:eula_agreement_timestamp]\n    webhook_info = assignment_configuration_tool_lookups.take&.webhook_info\n    should_add_proxy = false\n\n    if opts[:proxied_student]\n      current_user = original_student\n      original_student = opts[:proxied_student]\n      should_add_proxy = true\n    end\n\n    if opts[:submission_type] == \"student_annotation\"\n      raise \"Invalid Attachment\" if opts[:annotatable_attachment_id].blank?\n      raise \"Invalid submission type\" unless annotated_document?\n      # Prevent the case where a user clicks Submit on a stale tab, expecting\n      # to submit one set of work, only for another set to be submitted\n      # instead.\n      raise \"Invalid Attachment\" if opts[:annotatable_attachment_id].to_i != annotatable_attachment_id\n    end\n\n    # Only allow a few fields to be submitted.  Cannot submit the grade of a\n    # homework assignment, for instance.\n    opts.each_key do |k|\n      opts.delete(k) unless ALLOWABLE_SUBMIT_HOMEWORK_OPTS.include?(k.to_s)\n    end\n\n    comment = opts.delete(:comment)\n    group_comment = opts.delete(:group_comment)\n    group, students = group_students(original_student)\n    homeworks = []\n    primary_homework = nil","sourceCodeStart":2734,"sourceCodeEnd":2770,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/abstract_assignment.rb#L2734-L2770","documentation":"During assignment submission (student_annotation type), Canvas raises 'Invalid Attachment' when annotatable_attachment_id is blank, or when the attachment id submitted does not match the assignment's configured annotatable_attachment_id. This guards against stale tabs submitting annotation work against a different file.","triggerScenarios":"Submitting an ePortfolio/annotation attempt with no annotatable_attachment_id; a student with a stale browser tab submits after the teacher changed the assignment's annotated file so the ids differ.","commonSituations":"Teacher swaps the annotated document after students began work; student opens submit page, leaves it open, assignment config changes, then submits; direct API calls omitting the attachment id.","solutions":["Refresh the assignment page and resubmit so the current annotatable_attachment_id is used","Verify the API call includes a non-blank annotatable_attachment_id matching the assignment's annotatable_attachment_id","Re-publish/re-select the annotated document consistently, then have students reload before submitting"],"exampleFix":"# before\nsubmit({ submission_type: 'student_annotation' })\n# after\nsubmit({ submission_type: 'student_annotation', annotatable_attachment_id: assignment.annotatable_attachment_id })","handlingStrategy":"validation","validationCode":"const attachmentId = opts.annotatable_attachment_id;\nif (attachmentId == null || Number(attachmentId) === 0) {\n  throw new Error('annotatable_attachment_id is required for student_annotation submissions');\n}\nif (Number(attachmentId) !== Number(assignment.annotatable_attachment_id)) {\n  throw new Error('Stale tab: reload the page and resubmit');\n}","typeGuard":"function hasValidAnnotatableAttachment(opts, assignment) {\n  const id = Number(opts?.annotatable_attachment_id);\n  return Number.isInteger(id) && id > 0 && id === Number(assignment?.annotatable_attachment_id);\n}","tryCatchPattern":"try {\n  await submitAnnotation(submission);\n} catch (e) {\n  if (e.message === 'Invalid Attachment') {\n    promptReload('The annotated document changed. Please refresh and try again.');\n  }\n}","preventionTips":["Always read annotatable_attachment_id from freshly loaded assignment data","Invalidate/push refresh to open submission pages when the annotated document changes","Validate the attachment id client-side against the assignment before submit","Cover stale-tab resubmission in integration tests"],"tags":["submissions","student-annotation","attachments"],"backgroundTag":"missing-required-argument","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"}