instructure/canvas-lms · error · RuntimeError

PostPolicy is missing or does not respond to post_manually

Error message

PostPolicy is missing or does not respond to post_manually

What it means

ScheduledPost processing needs the assignment's PostPolicy to decide whether grades are posted manually. This guard fires when the post_policy is nil (no policy record exists for the assignment/course) or the object does not respond to post_manually, so the scheduled post cannot be safely evaluated. It runs after updating the scheduled_post timestamps so the job will not loop forever on a bad record.

Solutions

  1. Ensure a PostPolicy exists for the course/assignment (e.g. run the backfill/creation path that guarantees every assignment context has one)
  2. Check the assignment.context resolves to the correct course on the correct shard before processing
  3. If post_manually is false, the code intentionally skips — verify the policy is the real record, not a nil placeholder

Example fix

// before
unless post_policy&.post_manually
  ...
end
// after
unless post_policy&.respond_to?(:post_manually)
  raise "PostPolicy missing for assignment #{assignment.id}"
end
unless post_policy.post_manually
  ...
end
Defensive patterns

Strategy: validation

Validate before calling

policy = post_policy || assignment.post_policy
raise 'missing PostPolicy' unless policy.respond_to?(:post_manually)

Type guard

policy.respond_to?(:post_manually)

Try / catch

begin
  ScheduledPost.process(...)
rescue RuntimeError => e
  alert_missing_post_policy if e.message.include?('PostPolicy')
end

Prevention

When it happens

Trigger: A ScheduledPost job runs for an assignment whose course/assignment PostPolicy record is missing (post_policy nil); a stub/mock or mis-shaped post_policy object without a post_manually method is injected in a test or custom code path; data cleanup removed PostPolicy rows while ScheduledPost rows remain.

Common situations: Courses created via API or imports that skipped PostPolicy creation; sharded/multi-tenant environments where the PostPolicy lookup targets the wrong shard and returns nil; specs with double objects that don't implement post_manually.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/bc8ca80fe9205906. Report an issue: GitHub.

Appendix: source

Thrown at app/models/scheduled_post.rb:101

    forty_minutes_from_now = 40.minutes.from_now
    run_time = Time.current

    scheduled_posts_to_process = pending_comments_posting(forty_minutes_from_now).or(pending_grades_posting(forty_minutes_from_now))
    scheduled_posts_to_process = scheduled_posts_to_process.where(id: ids) if ids.present?

    scheduled_posts_to_process
      .preload(
        :post_policy,
        assignment: :context
      ).find_each do |scheduled_post|
      post_policy = scheduled_post.post_policy
      assignment = scheduled_post.assignment
      course = assignment.context

      # If post_manually is false, skip processing this scheduled post
      unless post_policy&.post_manually
        scheduled_post.update(post_grades_ran_at: run_time, post_comments_ran_at: run_time)
        raise "PostPolicy is missing or does not respond to post_manually" if post_policy.nil? || !post_policy.respond_to?(:post_manually)
      end

      if scheduled_post.should_post_comments_and_grades?
        # If both are set to the same time, then call process_assignment_posting only
        progress = course.progresses.new(tag: "scheduled_post_assignment_grades_and_comments:#{assignment.id}")
        process_assignment_posting(assignment:, progress:, run_at: scheduled_post.post_grades_at)
        scheduled_post.update(post_grades_ran_at: run_time, post_comments_ran_at: run_time)
        next
      end

      if scheduled_post.should_post_grades?(forty_minutes_from_now)
        progress = course.progresses.new(tag: "scheduled_post_assignment_grades:#{assignment.id}")
        process_assignment_posting(assignment:, progress:, run_at: scheduled_post.post_grades_at)
        scheduled_post.update(post_grades_ran_at: run_time)
      end

      next unless scheduled_post.should_post_comments?(forty_minutes_from_now)

View on GitHub (pinned to 1c9f0bb801)