{"record":{"id":"37ee79280e06687e","repo":"instructure/canvas-lms","slug":"notification-not-found","errorCode":null,"errorMessage":"Notification not found","messagePattern":"Notification not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/dismiss_account_notification.rb","lineNumber":30,"sourceCode":"# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# 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\nmodule Mutations\n  class DismissAccountNotification < BaseMutation\n    argument :notification_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"AccountNotification\")\n\n    def resolve(input:, **)\n      user = context[:current_user]\n      raise GraphQL::ExecutionError, I18n.t(\"Must be logged in\") unless user\n\n      notification = AccountNotification.find_by(id: input[:notification_id])\n      raise GraphQL::ExecutionError, I18n.t(\"Notification not found\") unless notification\n\n      closed_notifications = user.get_preference(:closed_notifications) || []\n      closed_notifications << notification.id unless closed_notifications.include?(notification.id)\n      user.set_preference(:closed_notifications, closed_notifications)\n\n      {}\n    end\n  end\nend\n","sourceCodeStart":12,"sourceCodeEnd":40,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/dismiss_account_notification.rb#L12-L40","documentation":"The mutation could not find an AccountNotification with the supplied notification_id: AccountNotification.find_by(id: ...) returned nil. The ID may be wrong, already deleted (notifications expire), or a relay/global-ID was passed where a numeric ID is expected despite the relay_or_legacy_id prepare function accepting both.","triggerScenarios":"Calling dismissAccountNotification with a notificationId that does not exist, was created for a different account/role the user cannot see, refers to a notification past its end_at (destroyed by AccountNotification.clean_up_old_notifications!), or a malformed ID string.","commonSituations":"Client cached a notification list that has since been cleaned up; double-dismiss race where the notification expired between list and dismiss; passing the wrong object's ID; hardcoding a dev-environment ID against production.","solutions":["Re-fetch current account notifications (query.account.notifications) and dismiss only IDs present in that response.","Confirm the notification exists and is within start_at/end_at for the user's account: AccountNotification.find(id) in a rails console.","Handle the error gracefully client-side: treat 'Notification not found' as success (already dismissed/expired) and refresh the list.","Ensure the ID is either the numeric id or a valid relay base64 global ID for AccountNotification."],"exampleFix":"// before\nawait client.mutate({ mutation: DISMISS, variables: { notificationId: cachedId } });\n// after\nconst ids = (await client.query({ query: GET_NOTIFICATIONS })).data?.account?.notifications?.map(n => n._id) ?? [];\nif (ids.includes(cachedId)) await client.mutate({ mutation: DISMISS, variables: { notificationId: cachedId } });\nelse refreshNotifications(); // already gone — ignore","handlingStrategy":"validation","validationCode":"const notif = notifications.find(n => n._id === notificationId);\nif (!notif) { refreshNotifications(); return; } // already gone\nawait dismiss(notificationId);","typeGuard":"const notificationExists = (notifications, id) => Array.isArray(notifications) && notifications.some(n => n && (n._id === id || n.id === id));","tryCatchPattern":"try { await dismiss(notificationId); }\ncatch (e) {\n  if (e.graphQLErrors?.some(g => g.message === 'Notification not found')) await refreshNotifications(); // treat as already dismissed\n  else throw e;\n}","preventionTips":["Only dismiss ids from a fresh notifications query","Treat not-found as idempotent success for dismiss operations","Refresh caches after notification cleanup jobs run","Validate id format (numeric or relay global id) before sending"],"tags":["graphql","not-found","canvas-lms"],"backgroundTag":"record-not-found","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"}