Skip to content

fix: schedule superseded-check cleanup (#261) - #262

Open
mithro wants to merge 2 commits into
mainfrom
issue/261-checks-cleanup
Open

fix: schedule superseded-check cleanup (#261)#262
mithro wants to merge 2 commits into
mainfrom
issue/261-checks-cleanup

Conversation

@mithro

@mithro mithro commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #261. _cancel_superseded_checks() — which cancels in-progress checks
superseded by a newer check on the same project file — was only reachable via
the combined checks_cleanup() task, which was never registered in
CELERY_BEAT_SCHEDULE. So in dev/stage/prod the superseded-cancel logic never
ran.

Root cause

  • The commit that introduced checks_cleanup() / _cancel_superseded_checks()
    (de548dd) only touched the task module and its tests — it never added a beat
    entry. The bare "checks-cleanup" schedule key has never existed on any
    branch.
  • checks_cleanup() also duplicated two cleanups
    (checks_cleanup_stale_files, checks_cleanup_stale_pending_tasks) that were
    already scheduled independently, so scheduling it as-is would double-run
    them. It is a leftover "combined task" anachronism in a codebase that had
    already moved to single-responsibility polling tasks.

Why the logic is still needed (not dead code)

The issue suspected _cancel_superseded_checks() might be dead. It is not.
Besides the beat task checks_drc_update_requeue (which only requeues
FINISHED checks), create_check_drc_update() has a second caller: the
manual view check_drc_update_requeue (views.py:969). Its requeue button is
gated on check.is_using_latest_precheck is Falsenot terminal status —
and in-progress checks carry a docker_image_digest, so they satisfy all the
create_check_drc_update guards. A staff/owner can therefore requeue a check
whose latest check is still running, leaving the older one superseded with
nothing to cancel it.

Changes

  • Extract the superseded-cancel logic into a single-responsibility
    checks_cleanup_superseded() task and schedule it at 60s in
    CELERY_BEAT_SCHEDULE (matches the per-state task architecture).
  • Remove the never-scheduled, redundant combined checks_cleanup().
  • Narrow a broad except Exception to InvalidStateTransitionError, matching
    the sibling checks_cleanup_stale_files.
  • Repoint the existing superseded-cancel tests at the new task.
  • Fix the misleading create_check_drc_update docstring and update the Celery
    task reference doc.

Verification

  • make lint, make type-check clean.
  • make test: 1279 passed. The one failure (test_detect_zip_file, a libmagic
    MIME quirk) is pre-existing on untouched main and unrelated to this change.
  • Runtime-verified the new task is registered in Celery and the old task/beat
    key are gone.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an automated cleanup process that runs every 60 seconds to cancel in-progress manufacturability checks superseded by newer checks.
    • Checks already being cancelled are skipped to prevent duplicate cancellation attempts.
  • Documentation

    • Updated the task reference documentation to describe the new periodic cleanup process.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change extracts superseded-check cancellation into a dedicated Celery task. Celery Beat runs it every 60 seconds. Documentation, model references, exports, and tests now use the dedicated task.

Changes

Scheduled Cleanup for Superseded Checks

Layer / File(s) Summary
checks_cleanup_superseded Task Implementation and Export
wafer_space/projects/tasks_checks.py
Adds the exported task, filters eligible superseded checks, skips CANCELLING checks, handles invalid transitions, and removes the combined cleanup task.
Scheduler Registration and Related References
config/settings/base.py, docs/celery_tasks_reference.md, wafer_space/projects/models.py
Schedules the task every 60 seconds and updates task documentation and the DRC-update check docstring.
Superseded Check Task Tests
wafer_space/projects/tests/test_tasks.py
Updates existing tests and adds coverage for checks already in CANCELLING.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CeleryBeat
  participant checks_cleanup_superseded
  participant CheckDatabase
  CeleryBeat->>checks_cleanup_superseded: Run every 60 seconds
  checks_cleanup_superseded->>CheckDatabase: Find superseded in-progress checks
  checks_cleanup_superseded->>CheckDatabase: Mark eligible checks as CANCELLING
Loading

Poem

🐰 A scheduled task now runs on time,
Superseded checks follow a cleaner line.
Newer checks guide the old ones away,
CANCELLING checks remain safe each day.
Tests confirm the task’s new way.

🚥 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 identifies the primary change: scheduling cleanup for superseded checks.
Linked Issues check ✅ Passed The PR resolves issue #261 by scheduling superseded-check cleanup, removing redundant orchestration, and updating tests and documentation.
Out of Scope Changes check ✅ Passed All changes support issue #261 and the PR objective of scheduling and documenting superseded-check cleanup.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 issue/261-checks-cleanup

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wafer_space/projects/tasks_checks.py (1)

1941-1944: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a deterministic tie-breaker for superseded detection.

Line 1943 only compares created_at, so two checks created with identical timestamps are treated as neither newer nor older. That can skip cancellation for a truly superseded check.

Suggested fix
+from django.db.models import Q
 from django.db.models import Subquery
@@
 newer_exists = ManufacturabilityCheck.objects.filter(
     project_file=OuterRef("project_file"),
-    created_at__gt=OuterRef("created_at"),
-)
+).filter(
+    Q(created_at__gt=OuterRef("created_at"))
+    | (
+        Q(created_at=OuterRef("created_at"))
+        & Q(id__gt=OuterRef("id"))
+    )
+)
🤖 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 `@wafer_space/projects/tasks_checks.py` around lines 1941 - 1944, The current
newer_exists query only compares created_at and can miss ties; update the filter
in the ManufacturabilityCheck subquery to include a deterministic tie-breaker by
checking either created_at__gt=OuterRef("created_at") OR
(created_at=OuterRef("created_at") AND pk__gt=OuterRef("pk")). Replace the
existing newer_exists definition to use Q(...) with the OR condition (using
OuterRef("created_at") and OuterRef("pk")) so checks with identical timestamps
are deterministically ordered by primary key.
🧹 Nitpick comments (1)
wafer_space/projects/tests/test_tasks.py (1)

2127-2179: ⚡ Quick win

Verify return values to match test patterns.

The three test methods in TestCancelSupersededChecks call checks_cleanup_superseded() but don't capture or verify the return value. Other cleanup task tests in this file consistently verify return values (e.g., TestChecksRetry asserts result["retried"] == 1, TestChecksCancelling asserts result["cancelled"] == 1).

♻️ Proposed enhancement to verify return values

For test_cancels_older_in_progress_check_when_newer_exists:

-        checks_cleanup_superseded()
+        result = checks_cleanup_superseded()

         old_check.refresh_from_db()
         assert old_check.status == ManufacturabilityCheck.Status.CANCELLING
+        assert result["cancelled"] == 1

For test_does_not_cancel_if_no_newer_check:

-        checks_cleanup_superseded()
+        result = checks_cleanup_superseded()

         check.refresh_from_db()
         assert check.status == ManufacturabilityCheck.Status.RUNNING
+        assert result["cancelled"] == 0

For test_does_not_cancel_finished_checks:

-        checks_cleanup_superseded()
+        result = checks_cleanup_superseded()

         old_check.refresh_from_db()
         assert old_check.status == ManufacturabilityCheck.Status.FINISHED
+        assert result["cancelled"] == 0
🤖 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 `@wafer_space/projects/tests/test_tasks.py` around lines 2127 - 2179, The tests
in TestCancelSupersededChecks call checks_cleanup_superseded() but don't assert
its return value; update each test
(test_cancels_older_in_progress_check_when_newer_exists,
test_does_not_cancel_if_no_newer_check, test_does_not_cancel_finished_checks) to
capture the result = checks_cleanup_superseded() and add assertions matching the
expected metrics (e.g., result["cancelled"] == 1 when an in-progress check is
cancelled, and result["cancelled"] == 0 for the other two cases) so the tests
follow the same return-value verification pattern as
TestChecksRetry/TestChecksCancelling.
🤖 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 `@docs/celery_tasks_reference.md`:
- Around line 52-53: The beat summary's "60 seconds" section is missing the
newly added task checks_cleanup_superseded; update the summary list in
docs/celery_tasks_reference.md to include checks_cleanup_superseded alongside
checks_drc_update_requeue under the 60 seconds/60s entry so the table and the
verbal summary match (ensure the summary references the exact task names
checks_cleanup_superseded and checks_drc_update_requeue).

---

Outside diff comments:
In `@wafer_space/projects/tasks_checks.py`:
- Around line 1941-1944: The current newer_exists query only compares created_at
and can miss ties; update the filter in the ManufacturabilityCheck subquery to
include a deterministic tie-breaker by checking either
created_at__gt=OuterRef("created_at") OR (created_at=OuterRef("created_at") AND
pk__gt=OuterRef("pk")). Replace the existing newer_exists definition to use
Q(...) with the OR condition (using OuterRef("created_at") and OuterRef("pk"))
so checks with identical timestamps are deterministically ordered by primary
key.

---

Nitpick comments:
In `@wafer_space/projects/tests/test_tasks.py`:
- Around line 2127-2179: The tests in TestCancelSupersededChecks call
checks_cleanup_superseded() but don't assert its return value; update each test
(test_cancels_older_in_progress_check_when_newer_exists,
test_does_not_cancel_if_no_newer_check, test_does_not_cancel_finished_checks) to
capture the result = checks_cleanup_superseded() and add assertions matching the
expected metrics (e.g., result["cancelled"] == 1 when an in-progress check is
cancelled, and result["cancelled"] == 0 for the other two cases) so the tests
follow the same return-value verification pattern as
TestChecksRetry/TestChecksCancelling.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 95c9e35c-18ad-4b6d-bf2b-29b2e8e375d1

📥 Commits

Reviewing files that changed from the base of the PR and between 636b39f and af82f75.

📒 Files selected for processing (5)
  • config/settings/base.py
  • docs/celery_tasks_reference.md
  • wafer_space/projects/models.py
  • wafer_space/projects/tasks_checks.py
  • wafer_space/projects/tests/test_tasks.py

Comment thread docs/celery_tasks_reference.md
mithro and others added 2 commits August 2, 2026 17:37
`_cancel_superseded_checks()` cancels in-progress checks that have been
superseded by a newer check for the same project file. It was only
reachable via the combined `checks_cleanup()` task, which was never
registered in `CELERY_BEAT_SCHEDULE` — so in dev/stage/prod the
superseded-cancel logic never ran.

Root cause: the commit that introduced `checks_cleanup()` /
`_cancel_superseded_checks()` (de548dd) only touched the task module and
its tests; it never added a beat-schedule entry. The combined task also
duplicated two cleanups (`checks_cleanup_stale_files`,
`checks_cleanup_stale_pending_tasks`) that were already scheduled
independently, so scheduling it as-is would double-run them.

The logic is genuinely needed: the manual DRC-update requeue view
(`check_drc_update_requeue`) can create a newer check for a file whose
latest check is still in progress, leaving the older one superseded with
nothing to cancel it. (The scheduled `checks_drc_update_requeue` beat
task only requeues FINISHED checks, so it never triggers this path.)

Changes:
- Extract the superseded-cancel logic into a single-responsibility
  `checks_cleanup_superseded()` task (matches the codebase's per-state
  task architecture) and schedule it at 60s in CELERY_BEAT_SCHEDULE.
- Remove the never-scheduled, redundant combined `checks_cleanup()`.
- Narrow the broad `except Exception` to `InvalidStateTransitionError`,
  matching the sibling `checks_cleanup_stale_files`.
- Repoint the existing superseded-cancel tests at the new task.
- Fix the misleading `create_check_drc_update` docstring and update the
  Celery task reference doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Status.in_progress() includes CANCELLING, but CANCELLING -> CANCELLING
is not a valid transition. A superseded check that was already marked
CANCELLING would be re-selected on every 60s run and produce a spurious
ERROR-level traceback until checks_cancelling completed it. Exclude
CANCELLING from the candidate query; the checks_cancelling task owns
completing those.

Adds a regression test asserting no ERROR logs are emitted and the
check is left untouched. Also fixes the stale test class docstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mithro
mithro force-pushed the issue/261-checks-cleanup branch from 330f279 to 9d91f68 Compare August 2, 2026 08:12

@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)
wafer_space/projects/tests/test_tasks.py (1)

2668-2691: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split this test by assertion concept.

This test verifies the result contract, absence of error logs, and persisted status. Put each behavior in a separate test.

🤖 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 `@wafer_space/projects/tests/test_tasks.py` around lines 2668 - 2691, Split
test_skips_checks_already_cancelling_without_error_logs into separate tests
covering the checks_cleanup_superseded result contract, absence of error logs,
and persisted CANCELLING status. Keep the shared factory setup and scenario
unchanged while giving each test a single assertion concept.

Source: Coding guidelines

🤖 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 `@wafer_space/projects/tests/test_tasks.py`:
- Around line 2668-2691: Split
test_skips_checks_already_cancelling_without_error_logs into separate tests
covering the checks_cleanup_superseded result contract, absence of error logs,
and persisted CANCELLING status. Keep the shared factory setup and scenario
unchanged while giving each test a single assertion concept.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d331173d-a948-4781-9d8a-6deea1d4ab18

📥 Commits

Reviewing files that changed from the base of the PR and between af82f75 and 9d91f68.

📒 Files selected for processing (5)
  • config/settings/base.py
  • docs/celery_tasks_reference.md
  • wafer_space/projects/models.py
  • wafer_space/projects/tasks_checks.py
  • wafer_space/projects/tests/test_tasks.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • config/settings/base.py
  • docs/celery_tasks_reference.md
  • wafer_space/projects/models.py

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.

Investigate: checks_cleanup / _cancel_superseded_checks is defined but never scheduled

1 participant