{"record":{"id":"bc9699e0586c5506","repo":"instructure/canvas-lms","slug":"failed-updating-an-event-in-the-series-update-not-saved","errorCode":null,"errorMessage":"Failed updating an event in the series, update not saved","messagePattern":"Failed updating an event in the series, update not saved","errorType":"http","errorClass":"ActiveRecord::Rollback","httpStatus":400,"severity":"error","filePath":"app/controllers/calendar_events_api_controller.rb","lineNumber":910,"sourceCode":"            if event.appointment_group && @event.appointment_group.appointments.count == 0\n              event.appointment_group.destroy(@current_user)\n            end\n          else\n            error = event.errors\n            raise ActiveRecord::Rollback\n          end\n        end\n\n        if params[:which] == \"following\"\n          # the remaining series just got shorter. reflect that in the rrrule\n          front_half_events = (find_which_series_events(target_event: @event, which: \"all\", for_update: false) - events).to_a\n          unless front_half_events.empty?\n            params_for_update_front_half = ActionController::Parameters.new(rrule: update_rrule_count_or_until(@event[:rrule], front_half_events.length)).permit(:rrule)\n            front_half_events.each do |event|\n              event.updating_user = @current_user\n              unless event.grants_any_right?(@current_user, session, :update)\n                error = { message: t(\"Failed updating an event in the series, update not saved\"), status: :unauthorized }\n                raise ActiveRecord::Rollback\n              end\n\n              unless event.update(params_for_update_front_half)\n                error = { message: t(\"Failed updating an event in the series, update not saved\") }\n                raise ActiveRecord::Rollback\n              end\n            end\n          end\n        end\n      end\n    end\n\n    return render json: error, status: :bad_request if error\n\n    @event.context.touch # assume all events in the series belong to the same context\n\n    json = (events + front_half_events).map do |event|\n      event.reload","sourceCodeStart":892,"sourceCodeEnd":928,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/calendar_events_api_controller.rb#L892-L928","documentation":"In CalendarEventsApiController#destroy_from_series, when deleting/updating one event in a recurring (rrule) series, each front-half sibling event must both be permitted (grants_any_right? :update) and save successfully with the new rrule params. Any failure sets an error hash with message 'Failed updating an event in the series, update not saved' (401 for permission, generic otherwise) and raises ActiveRecord::Rollback, aborting the whole transaction so no partial update persists.","triggerScenarios":"DELETE/update on a series event where at least one sibling event in the series is owned by/visible to a user lacking :update permission, or an event.update fails validation (e.g. invalid rrule count/until params, locked events, or concurrent modification).","commonSituations":"A user deletes their own occurrence of a shared series they don't fully manage; series containing events across contexts with differing permissions; validation failures from recomputed rrule count/until; stale events deleted concurrently by another user.","solutions":["Ensure the acting user has :update rights on every event in the series (manage all calendars involved), or delete only occurrences they own","Inspect the failing event's validation errors (event.errors) and fix the underlying validation problem","Retry after concurrent-modification conflicts, re-reading the series events","As an admin, operate as the series owner or use site-admin privileges"],"exampleFix":"// before\nevent.update(params_for_update_front_half) # fails validation, silent rollback\n// after\nunless event.update(params_for_update_front_half)\n  Rails.logger.warn(\"series event #{event.id}: #{event.errors.full_messages}\")\n  error = { message: event.errors.full_messages.to_sentence }\n  raise ActiveRecord::Rollback\nend","handlingStrategy":"try-catch","validationCode":"// Ruby pre-check before series operations\nunless series_events.all? { |e| e.grants_any_right?(user, session, :update) }\n  raise PermissionDenied, 'user cannot update all events in the series'\nend","typeGuard":null,"tryCatchPattern":"begin\n  destroy_from_series(event)\nrescue ActiveRecord::Rollback\n  # whole series update aborted; inspect error hash / event.errors, reload, then retry\nend","preventionTips":["Confirm update rights on every event in the series before bulk edits","Operate on series as the owner or with admin privileges","Reload the series before retrying after concurrent-modification failures"],"tags":["rails","api","calendar","rollback","permissions"],"backgroundTag":"database-write-failed","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"}