Add no-async-from-sync rule#1
Merged
Merged
Conversation
…ions Flags `asyncio.get_event_loop()`, `asyncio.run()`, `.run_until_complete()`, `asyncio.ensure_future()`, and `asyncio.create_task()` when called inside a non-async `def`. These patterns either deadlock against an already-running loop, silently create a new loop whose tasks never run, or raise RuntimeError. Module-level calls (the `if __name__ == "__main__": asyncio.run(main())` entrypoint) and calls inside `async def` are intentionally not flagged. `run` and `create_task` only match when the object is literally `asyncio` to avoid false positives on `app.run`, `thread.run`, `scheduler.create_task`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New rule
no-async-from-syncthat flags async-dispatch patterns inside sync functions — the shape LLMs love to produce when they've lost track of whether the surrounding function isasync def.Flagged patterns (inside a non-async
def):asyncio.get_event_loop()— deprecated, auto-creates a fresh loopasyncio.new_event_loop()asyncio.run(...)— reentrancy bomb if a loop is already runninganything.run_until_complete(...)— catchesloop.run_until_complete, aliasedevent_loop.run_until_complete, etc.asyncio.ensure_future(...)asyncio.create_task(...)Intentionally not flagged:
asyncio.run(main())(the canonicalif __name__ == "__main__":entrypoint)async def— caller hasawait,asyncio.get_running_loop()availableapp.run(...),thread.run(),scheduler.create_task(...)—runandcreate_taskonly match when the object is literallyasyncio, since bare.run/.create_taskare common on unrelated objects (Flask, threading, custom schedulers)Implementation notes
no-nested-tryandno-redundant-none-check: find nearest enclosingfunction_definition, check whether its first child is theasynctokenget_event_loop,new_event_loop,run_until_complete,ensure_future) match on attribute alone — resilient toimport asyncio as aiorun,create_task) require the object identifier to beasyncionest_asyncio, Jupyter, Django ORM adapters) can suppress with# slopcop: ignore[no-async-from-sync]— called out in the help textTest plan
cargo test --test no_async_from_sync— 15 new tests, all passcargo test— full suite, no regressionscargo build— cleancargo clippy— no new warnings from this change🤖 Generated with Claude Code