instructure/canvas-lms · error · RuntimeError
cannot change column: captions - locked by Master Course
Error message
cannot change column: captions - locked by Master Course
What it means
MediaTrack#check_for_restricted_updates runs as a validation before save. If the media track's attachment belongs to child content locked by a master course's :content restrictions, any change (e.g. uploading new captions) is blocked with 'cannot change column: captions - locked by Master Course'.
Solutions
- Unlock the related content in the master course (remove content lock) so child captions can change
- Make the caption edit in the master course and sync to associated courses
- If legitimate (e.g. migration/import), ensure skip_restrictions? applies (set @importing_migration or @skip_downstream_changes)
Example fix
// before attachment.media_tracks.create!(...) // after unless attachment.child_content_restrictions&.dig(:content) attachment.media_tracks.create!(...) end
Defensive patterns
Strategy: try-catch
Validate before calling
if track.attachment&.child_content_restrictions&.dig(:content) # surface UI message instead of attempting save end
Try / catch
begin
media_track.save!
rescue RuntimeError => e
raise unless e.message.include?('locked by Master Course')
flash[:error] = I18n.t('Captions are locked by the master course')
end Prevention
- Check child_content_restrictions before enabling caption editing in the UI
- Apply caption changes in the master course and let sync propagate
When it happens
Trigger: Creating/updating a MediaTrack whose attachment.child_content_restrictions includes :content while the object is child content of a blueprint course and restrictions are not skipped.
Common situations: Uploading replacement captions for a media file attached to a locked blueprint course item; imports touching master-locked media; users editing captions in a locked child course section.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- An institutional tag category did not pass validation
- app '# ' must be one of: #
- Can't create media submission without media object
- Cannot validate existence for resource type: #
- Caption cannot be empty.
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/7ad444f9786d26d5.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/media_track.rb:65
validates :kind, inclusion: { in: %w[subtitles captions descriptions chapters metadata] }
validates :content, presence: true, unless: -> { it.asr? && (it.processing? || it.failed?) }
validates :locale, format: { with: /\A[A-Za-z-]+\z/ }, uniqueness: { scope: :attachment_id, unless: -> { it.attachment_id.blank? } }
restrict_columns :content, %i[attachment_id content locale media_object_id webvtt_content external_id]
RE_LOOKS_LIKE_TTML = /<tt\s+xml/i
validates :content, format: {
without: RE_LOOKS_LIKE_TTML,
message: ->(_object, _data) { t("TTML tracks are not allowed because they are susceptible to xss attacks") }
}
# MasterCourses::CollectionRestrictor handles soft-deletes, but doesn't handle
# hard deletes well. One day we might want to standardize this to more hard
# deleted objects.
def check_for_restricted_updates
return true if skip_restrictions? || attachment&.skip_restrictions?
return unless attachment&.child_content_restrictions&.dig(:content)
raise "cannot change column: captions - locked by Master Course"
end
def set_media_and_attachment
self.attachment_id ||= media_object.attachment_id
self.media_object_id ||= attachment.media_object_by_media_id
end
def webvtt_content
super || content
end
def convert_srt_to_wvtt
return if content.blank?
if content.exclude?("WEBVTT") && (content_changed? || self["webvtt_content"].nil?)
srt_content = content.dup
srt_content.gsub!(/(:|^)(\d)(,|:)/, '\10\2\3')
srt_content.gsub!(/([0-9]{2}:[0-9]{2}:[0-9]{2})(,)([0-9]{3})/, '\1.\3')View on GitHub (pinned to 1c9f0bb801)