{"record":{"id":"34cb9a662aa83d8e","repo":"instructure/canvas-lms","slug":"missing-required-parameter-copied-at","errorCode":null,"errorMessage":"Missing required parameter: copied_at","messagePattern":"Missing required parameter: copied_at","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"app/models/lti/pns/lti_context_copy_notice_builder.rb","lineNumber":27,"sourceCode":"# the terms of the GNU Affero General Public License as published by the Free\n# Software Foundation, version 3 of the License.\n#\n# 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\nmodule Lti\n  module Pns\n    class LtiContextCopyNoticeBuilder < NoticeBuilder\n      attr_reader :params\n\n      def initialize(params = {})\n        raise ArgumentError, \"Missing required parameter: course\" unless params[:course]\n        raise ArgumentError, \"Missing required parameter: copied_at\" unless params[:copied_at]\n\n        @params = params\n        super()\n      end\n\n      def notice_type\n        Lti::Pns::NoticeTypes::CONTEXT_COPY\n      end\n\n      def custom_instructure_claims(_tool)\n        {}\n      end\n\n      def custom_ims_claims(_tool)\n        course = params[:course]\n\n        context = {\n          id: Lti::V1p1::Asset.opaque_identifier_for(course),","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/lti/pns/lti_context_copy_notice_builder.rb#L9-L45","documentation":"The same builder also requires params[:copied_at]; after the course check passes, initialize raises ArgumentError \"Missing required parameter: copied_at\" when that key is nil or missing. The timestamp of the copy is mandatory for the context-copy PNS notice payload.","triggerScenarios":"Calling Lti::Pns::LtiContextCopyNoticeBuilder.new(course: course) without :copied_at, or passing nil — e.g. when the copy completion timestamp wasn't recorded before the notice was built.","commonSituations":"Job handlers that pass raw job arguments missing the timestamp; timezone/serialization code that turned the timestamp into nil; newer callers added before the copied_at requirement existed.","solutions":["Pass the copy completion time as params[:copied_at] (e.g. Time.now.utc or the recorded copy timestamp).","Record the copied_at timestamp when the copy job starts/finishes and thread it through to the builder.","Assert copied_at.present? before constructing the builder to fail with clearer context.","Check for nil caused by serialization/deserialization of the job payload."],"exampleFix":"// before\nLti::Pns::LtiContextCopyNoticeBuilder.new({ course: course })\n// after\nLti::Pns::LtiContextCopyNoticeBuilder.new({ course: course, copied_at: Time.now.utc })","handlingStrategy":"validation","validationCode":"return nil unless params[:course].present? && params[:copied_at].present?\nLti::Pns::LtiContextCopyNoticeBuilder.new(params)","typeGuard":null,"tryCatchPattern":"begin\n  Lti::Pns::LtiContextCopyNoticeBuilder.new(course:, copied_at: copied_at)\nrescue ArgumentError => e\n  Rails.logger.warn(\"context copy notice skipped: #{e.message}\")\nend","preventionTips":["Always set copied_at = Time.now.utc at the copy completion point","Validate the timestamp survives job serialization (use iso8601 and parse back)","Keep constructor requirements and job payload in sync via a shared builder method"],"tags":["ruby","lti","argument-error","validation"],"backgroundTag":"missing-required-argument","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"}