Problem:
Recently, we started getting learner issues where a learner submits a correct answer, and the progress page shows 0/1 for some of the problems.
Upon investigating the logs, we have found that when the learner(User A) submits a response to a problem, the recalculate_subsection_grade_v3 task is submitted with the wrong user_id(User B). Learner submission history also shows the submission of User A, but the score says None/None. On the other hand, User B has no submission, but the score is 1/1. It is important to note that User B has never enrolled in the course.
Another thing that we noted is that most of the time, a task with the wrong user_id is submitted within a minute after User B has submitted an answer in one of their enrolled courses. This indicates that maybe the runtime/some other service is not able to pick the correct user when submitting the recalculate_subsection_grade_v3 task.
Submission History:
User A:
#2: 2025-11-28 09:33:40 UTC
Score: None / None
{
"attempts": 1,
"correct_map": {
"cbf22dccc3e24561bdd95280f9110196_2_1": {
"answervariable": null,
"correctness": "correct",
"hint": "",
"hintmode": null,
"msg": "",
"npoints": null,
"queuestate": null
}
},
"correct_map_history": [
{
"cbf22dccc3e24561bdd95280f9110196_2_1": {
"answervariable": null,
"correctness": "correct",
"hint": "",
"hintmode": null,
"msg": "",
"npoints": null,
"queuestate": null
}
}
],
"done": true,
"input_state": {
"cbf22dccc3e24561bdd95280f9110196_2_1": {}
},
"last_submission_time": "2025-11-28T09:33:38Z",
"score": {
"raw_earned": 1,
"raw_possible": 1
},
"score_history": [
{
"raw_earned": 1,
"raw_possible": 1
}
],
"seed": 1,
"student_answers": {
"cbf22dccc3e24561bdd95280f9110196_2_1": "choice_2"
},
"student_answers_history": [
{
"cbf22dccc3e24561bdd95280f9110196_2_1": "choice_2"
}
]
}
#1: 2025-11-28 09:08:38 UTC
Score: None / None
{
"input_state": {
"cbf22dccc3e24561bdd95280f9110196_2_1": {}
},
"score": {
"raw_earned": 0,
"raw_possible": 1
},
"seed": 1
}
User B:
#1: 2025-11-28 09:33:38 UTC
Score: 1.0 / 1.0
null
Responsible Code Flow
/openedx/edx-platform/xmodule/capa_block.py(1893)submit_problem()
1892 raise
-> 1893 published_grade = self.publish_grade()
1894
/openedx/edx-platform/xmodule/capa_block.py(1758)publish_grade()
1757
-> 1758 self.runtime.publish(self, 'grade', event)
1759
/openedx/edx-platform/xmodule/x_module.py(1504)publish()
1503 if publish_service := self._services.get('publish'):
-> 1504 publish_service.publish(block, event_type, event)
1505
/openedx/edx-platform/xmodule/services.py(240)publish()
239 if handle_event and not is_masquerading_as_specific_student(self.user, self.course_id):
--> 240 handle_event(block, event)
241 else:
/openedx/edx-platform/xmodule/services.py(280)_handle_grade_event()
279 if not self.user.is_anonymous:
--> 280 grades_signals.SCORE_PUBLISHED.send(
281 sender=None,
/openedx/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py(189)send()
188 for receiver in sync_receivers:
--> 189 response = receiver(signal=self, sender=sender, **named)
190 responses.append((receiver, response))
/openedx/edx-platform/lms/djangoapps/grades/signals/handlers.py(178)score_published_handler()
177 # Fire a signal (consumed by enqueue_subsection_update, below)
--> 178 PROBLEM_RAW_SCORE_CHANGED.send(
179 sender=None,
/openedx/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py(189)send()
188 for receiver in sync_receivers:
--> 189 response = receiver(signal=self, sender=sender, **named)
190 responses.append((receiver, response))
/openedx/edx-platform/lms/djangoapps/grades/signals/handlers.py(210)problem_raw_score_changed_handler()
209
--> 210 PROBLEM_WEIGHTED_SCORE_CHANGED.send(
211 sender=None,
/openedx/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py(189)send()
188 for receiver in sync_receivers:
--> 189 response = receiver(signal=self, sender=sender, **named)
190 responses.append((receiver, response))
/openedx/edx-platform/lms/djangoapps/grades/signals/handlers.py(236)enqueue_subsection_update()
235 return # If it's not a course, it has no subsections, so skip the subsection grading update
--> 236 recalculate_subsection_grade_v3.apply_async(
237 kwargs=dict(
/openedx/venv/lib/python3.11/site-packages/celery_utils/logged_task.py(24)apply_async()
23 """
---> 24 result = super().apply_async(args=args, kwargs=kwargs, **options)
25 log.info('Task {}[{}] submitted with arguments {}, {}'.format( # pylint: disable=consider-using-f-string
/openedx/venv/lib/python3.11/site-packages/celery/app/task.py(598)apply_async()
597 with denied_join_result():
--> 598 return self.apply(args, kwargs, task_id=task_id or uuid(),
599 link=link, link_error=link_error, **options)
/openedx/venv/lib/python3.11/site-packages/celery/app/task.py(826)apply()
825 )
--> 826 ret = tracer(task_id, args, kwargs, request)
827 retval = ret.retval
/openedx/venv/lib/python3.11/site-packages/celery/app/trace.py(453)trace_task()
452
--> 453 R = retval = fun(*args, **kwargs)
454 state = SUCCESS
/openedx/venv/lib/python3.11/site-packages/edx_django_utils/monitoring/internal/code_owner/utils.py(195)new_function()
194 set_code_owner_attribute_from_module(wrapped_function.__module__)
--> 195 return wrapped_function(*args, **kwargs)
196 return new_function
> /openedx/edx-platform/lms/djangoapps/grades/tasks.py(191)recalculate_subsection_grade_v3()
190 breakpoint()
--> 191 _recalculate_subsection_grade(self, **kwargs)
192
Suspected Service
EventPublishingService is the one that passes the user_id to different signals that eventually get passed to the recalculate_subsection_grade_v3.
We are looking for feedback/suggestions on what could be the possible issue with the scoring.
PS: We are running master branch of edx-platform at MIT.