Skip to content

Fix submission worker Celery queue for S3 retention tagging#5143

Merged
RishabhJain2018 merged 4 commits into
masterfrom
cursor/fix-celery-queue-submission-worker-44b5
Jul 9, 2026
Merged

Fix submission worker Celery queue for S3 retention tagging#5143
RishabhJain2018 merged 4 commits into
masterfrom
cursor/fix-celery-queue-submission-worker-44b5

Conversation

@RishabhJain2018

@RishabhJain2018 RishabhJain2018 commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the production AttributeError: 'NoneType' object has no attribute 'pop' raised when submission workers call enqueue_submission_artifact_retention_tagging() after evaluation completes.

The S3 retention feature enqueues tag_submission_artifact_retention_tags via Celery, but ECS submission worker task definitions never set CELERY_QUEUE_NAME. That left task_default_queue as None, causing Celery 4.3's expand_destination() to crash when routing the task.

Changes

  1. ECS task definitions — Pass CELERY_QUEUE_NAME into submission worker containers (task_definition and container_definition_submission_worker), injected at build time with validation so unset values are never rendered as the literal string "None".
  2. evalai/celery.py — Configure task_default_queue after config_from_object() and only when CELERY_QUEUE_NAME is set (or in DEBUG).
  3. enqueue_submission_artifact_retention_tagging() — Use apply_async() with an explicit queue when available; fall back to synchronous tagging when no queue is configured or Celery dispatch fails.
  4. Tests — Unit coverage for Celery enqueue, sync fallback, queue resolution, missing env validation, and broker failure fallback.

Deployment notes

  • Immediate relief: Existing workers tag artifacts synchronously until their ECS task definitions are refreshed.
  • Long-term: Run refresh_worker_task_definitions (or redeploy workers) so running containers receive CELERY_QUEUE_NAME and resume async tagging via Celery.

Testing

  • Added/updated unit tests in tests/unit/jobs/test_s3_retention.py and tests/unit/challenges/test_aws_utils.py.
  • Full backend suite not run in this cloud agent environment (Docker unavailable); please verify in CI.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • ECS worker task definitions now receive CELERY_QUEUE_NAME, and requests fail with a clear error if it isn’t set.
    • Retention-tagging tasks can now be enqueued onto the configured Celery queue when available.
  • Bug Fixes

    • Default queue selection no longer overwrites existing configuration when CELERY_QUEUE_NAME is unset/empty.
    • Retention-tagging falls back to synchronous tagging when no queue is configured or async enqueueing fails.
  • Tests

    • Added unit coverage for queue resolution, fallback behavior, and CELERY_QUEUE_NAME requirements for ECS task definitions.

Submission workers enqueue tag_submission_artifact_retention_tags after
evaluation, but ECS task definitions never set CELERY_QUEUE_NAME. That
left Celery task_default_queue as None and caused AttributeError in
expand_destination when calling .delay().

- Pass CELERY_QUEUE_NAME through COMMON_SETTINGS_DICT and worker task defs
- Stop forcing task_default_queue to None in evalai/celery.py
- Fall back to synchronous S3 tagging when no Celery queue is configured
- Add unit tests for enqueue and queue resolution helpers

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds CELERY_QUEUE_NAME handling to ECS task definitions, Celery configuration, and retention-tagging dispatch. Retention tagging now resolves a queue name before enqueueing, uses apply_async with that queue when available, and falls back to synchronous tagging on missing queue or scheduling failure.

Changes

Celery queue routing for retention tagging

Layer / File(s) Summary
ECS task definitions and AWS settings
apps/challenges/aws_utils.py, apps/challenges/task_definitions.py
CELERY_QUEUE_NAME is required when building the worker task definition and is added to the ECS environment templates.
Celery app default queue
evalai/celery.py
The Celery app now loads Django settings before queue selection and only overrides the default queue in non-DEBUG mode when CELERY_QUEUE_NAME is set.
Retention tagging queue dispatch
apps/jobs/s3_retention.py
Retention tagging resolves a queue name, schedules the Celery task with apply_async(..., queue=...) when possible, and falls back to synchronous tagging otherwise.
Unit tests for queue handling
tests/unit/challenges/test_aws_utils.py, tests/unit/jobs/test_s3_retention.py
Tests cover the new queue requirement, environment-driven queue selection, apply_async dispatch, and synchronous fallback paths.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing submission worker Celery queue handling for S3 retention tagging.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-celery-queue-submission-worker-44b5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/unit/jobs/test_s3_retention.py (1)

181-195: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider testing the empty-string CELERY_QUEUE_NAME edge case.

The queue resolution helper treats an empty-string env var as falsy and falls through to app.conf.task_default_queue. A test with CELERY_QUEUE_NAME="" would verify this boundary and prevent regressions if the env-var check is ever changed to a presence-only check.

✨ Optional test addition
+    `@patch.dict`("os.environ", {"CELERY_QUEUE_NAME": ""}, clear=True)
+    `@patch`("evalai.celery.app")
+    def test_get_celery_queue_for_retention_tagging_empty_env_falls_back(
+        self, mock_app
+    ):
+        mock_app.conf.task_default_queue = "celery_dev"
+        self.assertEqual(
+            "celery_dev", get_celery_queue_for_retention_tagging()
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/jobs/test_s3_retention.py` around lines 181 - 195, Add a test for
the empty-string CELERY_QUEUE_NAME edge case in
get_celery_queue_for_retention_tagging: verify that when os.environ contains
CELERY_QUEUE_NAME set to an empty string, the helper falls back to
evalai.celery.app.conf.task_default_queue instead of treating it as a valid
queue name. Reuse the existing test pattern alongside
test_get_celery_queue_for_retention_tagging_prefers_env and
test_get_celery_queue_for_retention_tagging_uses_app_default so the boundary
behavior is covered.
apps/jobs/s3_retention.py (1)

154-164: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Enqueue logic correctly dispatches async or falls back to sync.

The apply_async call with explicit queue=celery_queue and the return guard preventing the sync fallback from running are both correct. The deferred import of tag_submission_artifact_retention_tags avoids unnecessary imports when the sync path is taken.

Consider wrapping apply_async in a try/except to fall back to synchronous tagging if the broker is unavailable, improving resilience beyond the transition period:

💡 Optional resilience improvement
     celery_queue = get_celery_queue_for_retention_tagging()
     if celery_queue:
         from jobs.tasks import tag_submission_artifact_retention_tags

-        tag_submission_artifact_retention_tags.apply_async(
-            (submission.pk, list(artifact_paths)),
-            queue=celery_queue,
-        )
-        return
+        try:
+            tag_submission_artifact_retention_tags.apply_async(
+                (submission.pk, list(artifact_paths)),
+                queue=celery_queue,
+            )
+            return
+        except Exception:
+            logger.exception(
+                "Failed to enqueue retention tagging, "
+                "falling back to sync: submission_id=%s",
+                submission.pk,
+            )

     tag_submission_artifacts_for_retention(submission, artifact_paths)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/jobs/s3_retention.py` around lines 154 - 164, The enqueue path in the
retention tagging flow should gracefully fall back to synchronous processing if
Celery dispatch fails. Update the logic around
get_celery_queue_for_retention_tagging() and
tag_submission_artifact_retention_tags.apply_async to catch broker/dispatch
exceptions, log the failure, and then call
tag_submission_artifacts_for_retention as the fallback. Keep the existing queue
selection, deferred import, and early return behavior in the submission
retention handler.
evalai/celery.py (1)

9-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix correctly prevents None task_default_queue.

Moving config_from_object before the queue selection and guarding the task_default_queue assignment with a truthy check on CELERY_QUEUE_NAME directly addresses the root cause of the production crash.

Consider using a local variable to avoid the double os.environ lookup:

♻️ Optional refactor for clarity
 app.config_from_object("django.conf:settings")

+celery_queue_name = os.environ.get("CELERY_QUEUE_NAME")
 if settings.DEBUG:
     app.conf.task_default_queue = "celery_dev"
-elif os.environ.get("CELERY_QUEUE_NAME"):
-    app.conf.task_default_queue = os.environ["CELERY_QUEUE_NAME"]
+elif celery_queue_name:
+    app.conf.task_default_queue = celery_queue_name
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@evalai/celery.py` around lines 9 - 14, The Celery queue selection in
app/config_from_object and the task_default_queue assignment can still leave a
None value in production. Update the settings logic around
app.conf.task_default_queue so it only assigns CELERY_QUEUE_NAME when the
environment value is truthy, and keep the DEBUG branch handling intact. Use a
local variable for CELERY_QUEUE_NAME in this block to avoid repeated os.environ
lookups and make the guard explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/challenges/aws_utils.py`:
- Line 164: The COMMON_SETTINGS_DICT entry for CELERY_QUEUE_NAME currently
passes through os.environ.get("CELERY_QUEUE_NAME") and can render unset values
as the literal string "None" in ECS templates. Update the settings-building
logic in aws_utils.py so refresh_worker_task_definitions() never receives an
unset queue name, either by omitting the CELERY_QUEUE_NAME key when it is
missing or by validating and failing fast before the task definitions are
refreshed. Keep the fix localized around COMMON_SETTINGS_DICT and the
refresh_worker_task_definitions() flow so evalai/celery.py and
apps/jobs/s3_retention.py do not consume an invalid queue name.

---

Nitpick comments:
In `@apps/jobs/s3_retention.py`:
- Around line 154-164: The enqueue path in the retention tagging flow should
gracefully fall back to synchronous processing if Celery dispatch fails. Update
the logic around get_celery_queue_for_retention_tagging() and
tag_submission_artifact_retention_tags.apply_async to catch broker/dispatch
exceptions, log the failure, and then call
tag_submission_artifacts_for_retention as the fallback. Keep the existing queue
selection, deferred import, and early return behavior in the submission
retention handler.

In `@evalai/celery.py`:
- Around line 9-14: The Celery queue selection in app/config_from_object and the
task_default_queue assignment can still leave a None value in production. Update
the settings logic around app.conf.task_default_queue so it only assigns
CELERY_QUEUE_NAME when the environment value is truthy, and keep the DEBUG
branch handling intact. Use a local variable for CELERY_QUEUE_NAME in this block
to avoid repeated os.environ lookups and make the guard explicit.

In `@tests/unit/jobs/test_s3_retention.py`:
- Around line 181-195: Add a test for the empty-string CELERY_QUEUE_NAME edge
case in get_celery_queue_for_retention_tagging: verify that when os.environ
contains CELERY_QUEUE_NAME set to an empty string, the helper falls back to
evalai.celery.app.conf.task_default_queue instead of treating it as a valid
queue name. Reuse the existing test pattern alongside
test_get_celery_queue_for_retention_tagging_prefers_env and
test_get_celery_queue_for_retention_tagging_uses_app_default so the boundary
behavior is covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f0f1c922-f0c7-41c9-af53-df8731eee5e6

📥 Commits

Reviewing files that changed from the base of the PR and between 337b404 and 66b7a15.

📒 Files selected for processing (5)
  • apps/challenges/aws_utils.py
  • apps/challenges/task_definitions.py
  • apps/jobs/s3_retention.py
  • evalai/celery.py
  • tests/unit/jobs/test_s3_retention.py

Comment thread apps/challenges/aws_utils.py Outdated
- Validate CELERY_QUEUE_NAME before building worker task definitions
- Inject queue name at format time to avoid rendering None as "None"
- Fall back to sync S3 tagging when Celery broker dispatch fails
- Refactor celery.py queue selection with a local variable
- Add tests for missing/empty queue name and broker dispatch fallback

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
@RishabhJain2018 RishabhJain2018 marked this pull request as ready for review July 9, 2026 00:15
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

- Format long @patch.dict decorators with black
- Provide CELERY_QUEUE_NAME via autouse fixture for register task def tests

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/challenges/test_aws_utils.py (1)

6172-6246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify CELERY_QUEUE_NAME is passed through to the task definition template.

The happy-path tests mock task_definition.format and assert the return value, but never inspect the format call's kwargs to confirm CELERY_QUEUE_NAME is actually threaded into the template. If the updated_settings["CELERY_QUEUE_NAME"] = celery_queue_name line were accidentally removed, these tests would still pass silently.

As per path instructions, tests should actually assert behavior and not silently pass. Consider adding an assertion on mock_task_definition.format.call_args to verify the queue name is present.

♻️ Suggested assertion for each happy-path test

After each build_task_definition_dict call, add:

         task_def, error = build_task_definition_dict(challenge, "queue")
         self.assertIsNone(error)
         self.assertEqual(task_def, {"family": "queue"})
+        format_kwargs = mock_task_definition.format.call_args.kwargs
+        self.assertEqual(format_kwargs.get("CELERY_QUEUE_NAME"), "evalai-celery")
         mock_task_definition.format.assert_called_once()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/challenges/test_aws_utils.py` around lines 6172 - 6246, The
happy-path tests for build_task_definition_dict do not verify that
CELERY_QUEUE_NAME is actually forwarded into the task definition template.
Update the tests around mock_task_definition.format in both
test_build_task_definition_dict_normal_challenge and
test_build_task_definition_dict_code_upload_challenge to assert the format call
received the queue name in its kwargs. This will ensure the
updated_settings["CELERY_QUEUE_NAME"] assignment in build_task_definition_dict
is covered and cannot be removed without failing tests.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/challenges/test_aws_utils.py`:
- Around line 6172-6246: The happy-path tests for build_task_definition_dict do
not verify that CELERY_QUEUE_NAME is actually forwarded into the task definition
template. Update the tests around mock_task_definition.format in both
test_build_task_definition_dict_normal_challenge and
test_build_task_definition_dict_code_upload_challenge to assert the format call
received the queue name in its kwargs. This will ensure the
updated_settings["CELERY_QUEUE_NAME"] assignment in build_task_definition_dict
is covered and cannot be removed without failing tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e92bd577-4a16-46ce-b1d0-2e7fca3d146a

📥 Commits

Reviewing files that changed from the base of the PR and between d09bd30 and a690c95.

📒 Files selected for processing (2)
  • tests/unit/challenges/test_aws_utils.py
  • tests/unit/jobs/test_s3_retention.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/jobs/test_s3_retention.py

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.23%. Comparing base (b1d3f23) to head (61cd191).

Files with missing lines Patch % Lines
evalai/celery.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5143      +/-   ##
==========================================
+ Coverage   91.20%   91.23%   +0.03%     
==========================================
  Files         114      114              
  Lines        8865     8884      +19     
==========================================
+ Hits         8085     8105      +20     
+ Misses        780      779       -1     
Flag Coverage Δ
backend 93.79% <95.45%> (+0.02%) ⬆️
frontend 87.51% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Accounts & Authentication 97.40% <ø> (ø)
Challenges Management 96.19% <100.00%> (+<0.01%) ⬆️
Job Processing 90.10% <100.00%> (+0.27%) ⬆️
Participants & Teams 99.54% <ø> (ø)
Challenge Hosts 100.00% <ø> (ø)
Analytics 100.00% <ø> (ø)
Web Interface 100.00% <ø> (ø)
Frontend (Gulp) 87.51% <ø> (+0.02%) ⬆️
All Models 97.36% <ø> (ø)
All Views 100.00% <ø> (ø)
All Serializers 98.58% <ø> (ø)
Utility Functions 97.17% <100.00%> (+<0.01%) ⬆️
Core Configuration 78.94% <75.00%> (-3.41%) ⬇️
Files with missing lines Coverage Δ
apps/challenges/aws_utils.py 98.67% <100.00%> (+<0.01%) ⬆️
apps/challenges/task_definitions.py 100.00% <ø> (ø)
apps/jobs/s3_retention.py 75.00% <100.00%> (+4.34%) ⬆️
evalai/celery.py 72.72% <75.00%> (-5.06%) ⬇️

... and 1 file with indirect coverage changes

Files with missing lines Coverage Δ
apps/challenges/aws_utils.py 98.67% <100.00%> (+<0.01%) ⬆️
apps/challenges/task_definitions.py 100.00% <ø> (ø)
apps/jobs/s3_retention.py 75.00% <100.00%> (+4.34%) ⬆️
evalai/celery.py 72.72% <75.00%> (-5.06%) ⬇️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b1d3f23...61cd191. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RishabhJain2018 RishabhJain2018 merged commit d43da66 into master Jul 9, 2026
35 checks passed
@RishabhJain2018 RishabhJain2018 deleted the cursor/fix-celery-queue-submission-worker-44b5 branch July 9, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants