{"record":{"id":"f9054c62abae8e72","repo":"instructure/canvas-lms","slug":"invalid-media-object-id","errorCode":null,"errorMessage":"invalid media_object_id","messagePattern":"invalid media_object_id","errorType":"exception","errorClass":"ActiveRecord::RecordNotFound","httpStatus":404,"severity":"error","filePath":"app/controllers/media_objects_controller.rb","lineNumber":395,"sourceCode":"    js_env({ media_object: media_api_json }) if media_api_json\n\n    # Add custom body class for responsive padding control\n    @body_classes ||= []\n    @body_classes.push(\"immersive-media-view\", \"content-only\")\n\n    deferred_js_bundle :media_immersive_view\n    render html: '<div id=\"immersive_view_container\"></div>'.html_safe,\n           layout: \"layouts/application\"\n  end\n\n  private\n\n  def load_media_object_from_service\n    if params[:media_object_id].present? && !@media_object\n      # Unfortunately, we don't have media_object entities created for everything,\n      # so we use this opportunity to create the object if it does not exist.\n      @media_object = MediaObject.create_if_id_exists(params[:media_object_id])\n      raise ActiveRecord::RecordNotFound, \"invalid media_object_id\" unless @media_object\n\n      @media_object.delay(singleton: \"retrieve_media_details:#{@media_object.media_id}\").retrieve_details\n      increment_request_cost(MISSED_MEDIA_ADDITIONAL_COST)\n    end\n\n    @media_object&.viewed!\n  end\n\n  def permitted_viewer_restrictions\n    @permitted_viewer_restrictions ||=\n      params[:viewer_restrictions]&.permit(MediaObject::VIEWER_RESTRICTION_KEYS)\n    @permitted_viewer_restrictions ||= {}\n  end\nend\n","sourceCodeStart":377,"sourceCodeEnd":410,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/media_objects_controller.rb#L377-L410","documentation":"Raised in MediaObjectsController#load_media_object_from_service when params[:media_object_id] is present but MediaObject.create_if_id_exists returns nil — meaning the id is not a well-formed media object identifier (create_if_id_exists only materializes objects for valid existing media ids). It surfaces to the user as a 404.","triggerScenarios":"Requesting media play/download/stream endpoints with a fabricated, deleted, or cross-shard media_object_id; client caching an id of a MediaObject that was deleted; passing a wrong id type (e.g. course media id vs media object id).","commonSituations":"Frontend kept an old media id after the attachment was re-uploaded; sharing media URLs between Canvas shards; copying ids from a different environment (test vs production).","solutions":["Verify the media_object_id exists via the media_objects API and use the correct current id","Re-upload/re-create the media object if it was deleted and obtain its new id","Ensure the id belongs to the same shard/account the request is made against","Fix the client to handle 404 and refetch the media id from the source content"],"exampleFix":"// before\ngetMedia('/media_objects/abc123/play') // stale id\n// after\nconst mo = await getMediaObject(courseId); // fetch fresh media_object_id\ngetMedia(`/media_objects/${mo.id}/play`)","handlingStrategy":"type-guard","validationCode":"const exists = await fetch(`/api/v1/media_objects/${id}`).then(r => r.ok).catch(() => false);\nif (!exists) throw new Error(`media_object ${id} not found`);","typeGuard":"const isValidMediaId = (id) => typeof id === 'string' && id.length > 0 && /^\\d+$|^mce[a-z0-9]+$/i.test(id);","tryCatchPattern":"begin\n  media_object = MediaObject.create_if_id_exists(id)\nrescue ActiveRecord::RecordNotFound\n  render json: { error: 'media not found, refresh from source content' }, status: :not_found\nend","preventionTips":["Refetch media ids after re-uploads instead of caching","Keep media ids shard/environment-specific","Treat 404 on media objects as refetch-and-retry signal"],"tags":["ruby","media","record-not-found"],"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"}