{"record":{"id":"d045a32ffc1d3c6a","repo":"instructure/canvas-lms","slug":"no-drafts-found","errorCode":null,"errorMessage":"no drafts found","messagePattern":"no drafts found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"warning","filePath":"app/graphql/mutations/delete_submission_draft.rb","lineNumber":32,"sourceCode":"# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass Mutations::DeleteSubmissionDraft < Mutations::BaseMutation\n  graphql_name \"DeleteSubmissionDraft\"\n\n  argument :submission_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"Submission\")\n\n  field :submission_draft_ids, [ID], null: true\n\n  def resolve(input:)\n    submission = Submission.active.find(input[:submission_id])\n    verify_authorized_action!(submission, :submit)\n\n    raise GraphQL::ExecutionError, \"no drafts found\" if submission.submission_drafts.none?\n\n    context[:submission] = submission\n    submission_draft_ids = submission.submission_draft_ids\n    submission.delete_submission_drafts!\n\n    { submission_draft_ids: }\n  rescue ActiveRecord::RecordNotFound\n    raise GraphQL::ExecutionError, \"not found\"\n  end\n\n  def self.submission_draft_ids_log_entry(_draft_ids, context)\n    # Follow the lead of CreateSubmissionDraft and return the submission object\n    # for logging\n    context[:submission]\n  end\nend\n","sourceCodeStart":14,"sourceCodeEnd":49,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/delete_submission_draft.rb#L14-L49","documentation":"The deleteSubmissionDraft mutation raises \"no drafts found\" when the submission has no associated submission_drafts. The submission exists and the user can :submit, but there is nothing to delete, so the mutation refuses rather than no-op.","triggerScenarios":"Calling deleteSubmissionDraft for a submission where submission.submission_drafts.none? — e.g. the user never started a draft, drafts were already deleted, or drafts belong to a different attempt.","commonSituations":"Double-clicking a discard-drafts button, calling the mutation after drafts were already committed to a submission, or ids from a previous submission attempt.","solutions":["Check submission.submission_drafts.any? client-side before calling the mutation","Treat this error as an idempotent success if the goal is simply 'no drafts remain'","Verify you are targeting the correct submission_id (current attempt)","Re-create a draft before deleting if testing the delete path"],"exampleFix":"// before\nraise GraphQL::ExecutionError, \"no drafts found\" if submission.submission_drafts.none?\n// after\nif submission.submission_drafts.none?\n  return { submission_draft_ids: [] } # idempotent no-op\nend","handlingStrategy":"fallback","validationCode":"// query drafts first\nconst s = await query(submission, { id });\nif (!s?.submissionDrafts?.length) return { submissionDraftIds: [] };","typeGuard":"function hasDrafts(submission) {\n  return Array.isArray(submission?.submissionDrafts) && submission.submissionDrafts.length > 0;\n}","tryCatchPattern":"try {\n  return await client.mutate(DELETE_SUBMISSION_DRAFT, { submissionId });\n} catch (e) {\n  if (e.message === \"no drafts found\") return { submissionDraftIds: [] };\n  throw e;\n}","preventionTips":["Check draft presence before rendering discard-draft UI","Treat deletion as idempotent client-side","Clear draft state after successful submission","Use the submission id of the current attempt"],"tags":["graphql","submission-draft","empty-state","idempotency"],"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"}