QuerySet methods don't change the queryset. They return a new one. So this line does nothing:
qs = Product.objects.all() if only_dirty: qs.filter(dirty=True) # returns a new queryset; qs is unchanged return qs It reads fine and it runs fine. Tests often still pass too, because the unfiltered result usually contains the rows the test was looking for.
I found these by running a check for calls whose result is thrown away over about 25 open source Python projects, then going through every hit by hand. Most were noise. Eleven were real bugs. These four are reported so far:
Saleor: an option that was only supposed to recalculate prices for products that changed ended up recalculating every discounted product. The existing test passed anyway: the one listing that should have been left alone was skipped for an unrelated reason.
https://github.com/saleor/saleor/pull/19984
Wagtail: a superuser who submitted a page for moderation got the moderation email about their own submission. The one line meant to exclude the submitter was the discarded call, so nobody was excluded at all. The existing test missed it because the user submitting in the test wasn't a superuser. When the reviewer asked for a test in a working project, I ran it in a fresh Wagtail project before and after the fix; the results are in the thread.
https://github.com/wagtail/wagtail/pull/14790
Tandoor: a ?category= filter on an API endpoint was never applied. There was a "TODO add tests for filter" right above it.
https://github.com/TandoorRecipes/recipes/pull/4917
Mealie: this one isn't Django. It's SQLAlchemy, whose Select objects work the same generative way. A generic filter meant to keep each household's data separate was never applied. Recipe pages had their own explicit filters, so the bug never showed there. The maintainers fixed it after the report.
https://github.com/mealie-recipes/mealie/issues/8964
The pattern survived code review in every case. It looks like correct code, because everything except the assignment is correct.
(The check came out of readfirst, an MIT CLI I'm building: https://github.com/jeonghun917/readfirst. I used Claude Code to help trace call paths and write the test scripts; the reports are mine.)
What I learned
- In review, a bare
qs.something(...)on its own line is worth a second look. The model, the field and the value can all look right, and the line still does nothing. - It's easy to write when you're in a hurry. Coding agents write it too.
If you want to catch it in your code
- Search your code for lines that start with a queryset variable and a method call, with nothing assigned:
^\s*\w+\.(filter|exclude|order_by|select_related|prefetch_related|annotate|distinct)\(You'll get some noise, but it's a fast pass. Linters don't help here: pylint'sexpression-not-assignedskips calls, because a call may have side effects.
A few more are pending in other projects. I'll add them once the maintainers have seen the reports.