async-doctor
Your agent blocks the event loop. This catches it.
Zero-config static analysis for Python: finds sync/blocking IO reachable from
async def code, and reports evidence an agent can act on. Non-goals (no
runtime probing, no Trio-only / gevent-eventlet modelling) live in
ARCHITECTURE.md.
Install
uvx async-doctor .
pipx run async-doctor .
pip install async-doctor && async-doctor .
# From this repo (wheel):
maturin build --release
uvx --from ./target/wheels/async_doctor-*.whl async-doctor --version
# From source (Rust toolchain required):
cargo install --git https://github.com/maolonglong/async-doctor async_doctor
Activate the project venv (or set VIRTUAL_ENV) before scanning so third-party
imports resolve. Without a project environment, results silently degrade —
async-doctor reports the interpreter/venv it used and prints a short note.
Quick start
async-doctor .
async-doctor --score .
async-doctor --format json .
async-doctor --format markdown . >> "$GITHUB_STEP_SUMMARY"
async-doctor --format sarif . > async-doctor.sarif
async-doctor explain blocking-sleep
async-doctor why app.py:12 --path .
async-doctor install-skill --path .
# CI / agent loops (full analysis; filtered reporting):
async-doctor --changed .
async-doctor --changed=origin/main .
async-doctor --write-baseline baseline.json .
async-doctor --baseline baseline.json .
Every finding includes the full call chain (file:line per hop), a concrete fix
hint, a stable rule id, a docsAnchor into docs/rules.md, and
a runnable why command. No config file — flags and inline comments only.
time.sleep(1) # async-doctor: ignore[blocking-sleep]: brief sync boundary in CLI wrapper
Unused ignores fail the run. Exit 0 = clean, 1 = findings (or unused
suppressions), 2 = tool error. Incomplete analysis sets complete=false and
does not use exit 2. Candidates and advice never affect the exit code.
By default, entries under tests/ / docs/ / examples/ / scripts/ /
hidden dirs are skipped (--include-non-production to include).
reason=unresolved candidates are hidden unless --show-unresolved.
What it detects
- Sync/blocking IO reachable from
async def(sleep, socket, file, subprocess, locks, smtplib/ftplib, zip/tar, glob/pathlib walks,input, futures waits) with prove-only conditions and entry labels - Site-packages follow down to stdlib sinks; third-party hops marked in JSON
- Third-party rules:
requests, synchttpx, redis, SQLAlchemy, psycopg, boto3 client/resource calls, sync OpenAI/Anthropic, and more — async twins never fire as findings - Higher-order / offload:
asyncio.to_thread, executors, inferred wrappers - Advice (
offload-has-async-twin,dangling-task): offload cuts that only reach sinks with a high-confidence same-package async twin installed in the scanned venv, and bareasyncio.create_task/ensure_futurestatements that discard the returnedTask(non-blocking; SARIFnote; text Advice section, capped unless--expand) - Errors:
nested-event-loop(asyncio.run/ … from async without offload; loop-state guards likeif loop is None/ early-returnif loop is not None: returnare dropped),offload-coroutine(offload target isasync def),sync-with-async-cm(syncwithentering an async-only context manager)
GitHub Actions (SARIF / code scanning)
Upload SARIF so findings appear in GitHub code scanning. async-doctor exits
1 when it finds issues — append || true (same pattern as GitHub’s ESLint
SARIF example) so the upload step still runs:
name: async-doctor
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
# required for private repositories
actions: read
contents: read
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.3.3
- name: Install project deps
run: uv sync --frozen
- name: Run async-doctor
run: |
source .venv/bin/activate
uvx async-doctor --format sarif . > async-doctor.sarif || true
- uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: async-doctor.sarif
Large repositories / memory
On a 4-core Cloud VM (Linux x86_64), the litellm/ package subdirectory
(3009 .py files, with venv) cold-scans in about 8.40 s at
~1.11 GiB peak RSS — see docs/benchmark.md for all
corpora. Prefer a package subdirectory (async-doctor path/to/pkg) when memory
is tight, and --max-findings N when the JSON payload is what matters (litellm
shrinks from ~38 MB to <1 MB at N=50 while score/sinkSites stay full-report).
Warm runs reuse the whole-scan cache when inputs are unchanged.
Benchmark
Public cold/warm numbers on pinned asyncio corpora:
docs/benchmark.md. Reproduce with ./bench/run.sh.
Agent integrations
# Built-in zero-Node installer (writes .agents/skills/async-doctor/SKILL.md + .claude/skills symlink):
async-doctor install-skill --path .
# Or via Node skills CLI:
npx skills add maolonglong/async-doctor
For GitHub code scanning, see the SARIF workflow above.
License
MIT. Bundled typeshed stubs (via ty_vendored) are Apache-2.0; see
THIRD_PARTY_NOTICES.md.
Metadata
Release files for async-doctor 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| async_doctor-0.1.1.tar.gz | 291.2 kB | Without provenance | Details for async_doctor-0.1.1.tar.gz |
Built distributions (wheels)
Total release size: 79.3 MB
Release files / async_doctor-0.1.1.tar.gz
| Download URL | async_doctor-0.1.1.tar.gz |
|---|---|
| Size | 291.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eec0f8ee4e333b0cc268f34b0f9f8d9f5d397619feb2c1162f9658b78db175ed
|
|
BLAKE2b-256 checksum How to use checksums |
4f6137f367a260c70e3f4ac1267993947baf6715c02daf5a8916d07fb7b10349
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-win_amd64.whl
| Download URL | async_doctor-0.1.1-py3-none-win_amd64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
79d29170b20018db1cd6ef1d6ab8fb8af7667aa886f481e1bce6e2e2ff46bfe3
|
|
BLAKE2b-256 checksum How to use checksums |
deff6e430604e1d288bd30224efe12fec8c64cd6c3381bb61d5843c203b790af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-musllinux_1_2_x86_64.whl
| Download URL | async_doctor-0.1.1-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 11.8 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
26eca627faa079c4535186e28d53f2f96ca9bfced5502bc68b7d9aef822cb5de
|
|
BLAKE2b-256 checksum How to use checksums |
cd0740822c4026cfd1ee0eed573811d5452f4c3db0b08cda2b3bf75c4f076a86
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-musllinux_1_2_aarch64.whl
| Download URL | async_doctor-0.1.1-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 10.9 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
26f20dbab0bfd2c1c5dc3769b8ef14df23dfd9db6033fd78834f77d68c850750
|
|
BLAKE2b-256 checksum How to use checksums |
77a9d33ac47b0adc9a62ab31af8de80265c5324873265578749744c6f53f958a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | async_doctor-0.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5b429072b193227324c7f3793112e0dc0610cb44794706c6ef217be4f43c1133
|
|
BLAKE2b-256 checksum How to use checksums |
de7fbd762cfcad8eb1c861803ff1ce105b48d76fe267fb8dfc37e3d0a47a6f29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | async_doctor-0.1.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 10.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
3574e1662f02981adac0fdbd75e27ed1c4f9f81fb4ee22f3d3756492d1fb5736
|
|
BLAKE2b-256 checksum How to use checksums |
832224f794aff0f99b5b911ae3a43e6d118e7f9f26c1b4bcd3a84ef084cab41c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-macosx_11_0_arm64.whl
| Download URL | async_doctor-0.1.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 10.8 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
863c2d88446f19cb6ee55037b6dd9acd3404092a2e8e8c4f812b84a9b586bc4f
|
|
BLAKE2b-256 checksum How to use checksums |
70923917c9c78c4973b55f56753203c67434f99fcad2f0f966826605e7282c5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / async_doctor-0.1.1-py3-none-macosx_10_12_x86_64.whl
| Download URL | async_doctor-0.1.1-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 11.4 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
8b517a5b0af3caf3b465a31b224b8fa05adbe3ec75b2d7a95b07f470ffc4785b
|
|
BLAKE2b-256 checksum How to use checksums |
ea00d027ba6b2617051807111abd47baf07b09805bd8dfe0f28858b6f758900c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for async-doctor 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.