instructure/canvas-lms · error
timetable codes can't be blank
Error message
timetable codes can't be blank
What it means
Courses::TimetableEventBuilder#create_or_update_events uses each event hash's :code as the unique timetable_code for syncing (creating/deleting calendar events). If any provided hash has a blank code, the sync keys are ambiguous, so it raises 'timetable codes can't be blank'.
Solutions
- Ensure every event hash includes a non-blank :code before calling create_or_update_events.
- Generate/derive codes at the data-source level (e.g. from day+start+end) for rows missing them.
- Filter out or repair rows with blank codes and report them to the source-data owner.
Example fix
// before
builder.create_or_update_events(rows.map { |r| {start_at: r.start, end_at: r.fin} })
// after
events = rows.map { |r| {code: r.code.presence || "#{r.day}-#{r.start}", start_at: r.start, end_at: r.fin} }
builder.create_or_update_events(events) Defensive patterns
Strategy: validation
Validate before calling
blank = event_hashes.select { |h| h[:code].blank? }
raise 'blank codes: #{blank.size}' if blank.any? Type guard
def all_coded?(event_hashes)
event_hashes.all? { |h| h[:code].present? }
end Prevention
- Validate imported timetable rows before building event hashes
- Default-generate codes from day/start/end when the source omits them
- Log and quarantine rows with missing identifiers instead of passing them through
When it happens
Trigger: Calling builder.create_or_update_events(event_hashes) where any hash omits :code or passes an empty string/nil code; rows from a source timetable missing the identifier column.
Common situations: CSV/timetable-import rows with a missing identifier cell; upstream data changes dropping the code field; callers assuming codes are auto-generated — generation only happens internally per event, not for blank inputs here.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- invalid options - # (must be in # )
- require course
- A course did not pass validation
- A # user did not pass validation (user: # , # : # , error…
- A maximum of 50 assessees can be provided at once
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/a94ff322bacec04d.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/courses/timetable_event_builder.rb:79
event_hash = { start_at: event_start_at, end_at: event_end_at }
event_hash[:location_name] = location_name if location_name
event_hashes << event_hash
end
current_date += 7 # move to next week
end
end
end
event_hashes
end
# expects an array of hashes
# with :start_at, :end_at required
# and optionally :location_name (other attributes could be added here if so desired)
# :code can be used to give it a unique identifier for syncing (otherwise will be generated based on the times)
# :title can be used to give a title to the event (otherwise a the name of the associated course will be used)
def create_or_update_events(event_hashes)
timetable_codes = event_hashes.pluck(:code)
raise "timetable codes can't be blank" if timetable_codes.any?(&:blank?)
# destroy unused events
event_context.calendar_events.active.for_timetable.where.not(timetable_code: timetable_codes)
.update_all(workflow_state: "deleted", deleted_at: Time.now.utc)
existing_events = event_context.calendar_events.where(timetable_code: timetable_codes).to_a.index_by(&:timetable_code)
event_hashes.each do |event_hash|
CalendarEvent.unique_constraint_retry do |retry_count|
code = event_hash[:code]
event = event_context.calendar_events.where(timetable_code: code).first if retry_count > 0
event ||= existing_events[code] || create_new_event(event_hash)
sync_event(event, event_hash)
end
end
end
ALLOWED_TIMETABLE_KEYS = %i[weekdays course_start_at course_end_at start_time end_time location_name].freeze
def process_and_validate_timetables(timetable_hashes)View on GitHub (pinned to 1c9f0bb801)