Nobody has started this yet — be first.
Business impact
This project's own README makes a specific, deliberate claim: cancelling a job is real asyncio.Task cancellation, not a status flag the simulated worker happens to also set. That distinction matters beyond API-response cosmetics -- imagine gating billing on whether a job was genuinely cancelled (task.cancelled()) versus completed some other way, specifically so a user who cancels their own job isn't charged for it. Right now, every "cancelled" job's underlying asyncio.Task actually reports as having finished normally. A billing check built on that ground truth would incorrectly treat every user-cancelled job as a normal completion -- and this is exactly the "looks right at the API layer, wrong one layer down" failure mode that black-box HTTP tests structurally cannot catch.
Problem
The cleanup (writing status="cancelled", bumping the metric, notifying the registry) all happens correctly, but the except block ends there -- there's no raise to let the CancelledError continue propagating, so the coroutine returns normally from asyncio's point of view.
Current behavior
run_job()'s except asyncio.CancelledError branch writes status="cancelled" and returns without a trailing raise, so the coroutine returns normally and task.cancelled() reports False even after a successful cancel.
Expected behavior
After POST /jobs/{id}/cancel, the underlying asyncio.Task that ran the job must itself report task.cancelled() is True -- not just the job document's status field.
Steps to reproduce
pytest practice_tickets/tests/test_ticket_13_cancellederror_swallowed.py -vasyncio.Task from the registry, call , then check .Why this matters
This is the standard, well-documented asyncio pitfall: catching asyncio.CancelledError to run cleanup is completely correct and necessary, but the cleanup handler must re-raise it once done, or the cancellation itself is silently absorbed. It's an easy step to drop, because the symptom (the task's own terminal state) is invisible to anything that only checks the job document -- which is every existing black-box test in this project.
Suggested approach
Compare this except asyncio.CancelledError branch against the except Exception branch right below it in the same function: one of them ends by letting its exception continue propagating, the other doesn't (correctly -- a caught Exception here is meant to be fully handled, turned into a failed status, not re-raised). Which one should CancelledError behave like?
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_13_cancellederror_swallowed.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/cancellederror-swallowedFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/cancellederror-swallowedSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inPOST /jobs/{id}/canceltask.cancelled()task.cancelled() prints False and task.exception() prints None, even though the API just reported the job as cancelled.http://localhost:8000localhost:6379curl localhost:8000/healthz{"status": "ok", "redis": true}Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest file in practice_tickets/tests/:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest practice_tickets/tests/test_ticket_01_completed_jobs_counted_as_failed.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Each ticket test runs in its own pytest process with a 60s timeout, so one hung/crashed ticket can't take down the whole run or hide another ticket's result. Run pytest tests/ (the project's own suite, against fakeredis, hermetic and fast) after each fix to check for regressions -- the README documents exactly which fixes are expected to change that suite's results.
Level 1
Fix a bug
Read existing behaviour, correct it.