DEV Community

Bhuvansh
Bhuvansh

Posted on

I built pyrift: a Python tool that catches runtime bugs linters can't see

The problem

Every Python developer has hit a bug that:

  • Passes all linting checks
  • Passes all type checking
  • Only appears at runtime, often in production

These are not syntax errors. They are not CVEs. They are silent behaviour differences — code that runs without errors but produces wrong results, leaks resources, or crashes only in certain environments.

I got frustrated with this pattern while working on CPython contributions and maintaining a PyPy review toolkit. So I built pyrift.


What pyrift does

pip install pyrift
pyrift scan .
Enter fullscreen mode Exit fullscreen mode

It statically analyses your Python code and flags patterns that behave differently across CPython versions or between CPython and PyPy.

Example output

[ERROR] src/server.py:42 CPY002: Exception.add_note() requires Python 3.11+
→ Guard with: if sys.version_info >= (3, 11): e.add_note(...)

[ERROR] src/compat.py:17 CPY007: Module removed in Python 3.13
→ Find a third-party replacement for 'cgi' on PyPI

[WARNING] src/resource.py:88 PPY001: Relying on del for resource cleanup breaks on PyPy
→ Use context managers (with statement) or try/finally blocks

Scanned 23 file(s). Found 2 error(s), 1 warning(s). Score: 77/100
Enter fullscreen mode Exit fullscreen mode

Real examples of what it catches

CPython version differences

Exception.add_note() — silent AttributeError on Python 3.10

try:
    connect()
except ConnectionError as e:
    e.add_note("Check your network")  # AttributeError on 3.10
    raise
Enter fullscreen mode Exit fullscreen mode

add_note() was added in Python 3.11 (PEP 678). On 3.10 it raises AttributeError inside the except block — the worst possible place to get an error.


asyncio.TaskGroup — AttributeError on Python 3.10

async with asyncio.TaskGroup() as tg:  # AttributeError on 3.10
    tg.create_task(fetch())
Enter fullscreen mode Exit fullscreen mode

TaskGroup was added in Python 3.11. This crashes silently on 3.10 with no hint of why.


21 stdlib modules removed in Python 3.13

import cgi        # ModuleNotFoundError on Python 3.13
import telnetlib  # ModuleNotFoundError on Python 3.13
import asynchat   # ModuleNotFoundError on Python 3.13
Enter fullscreen mode Exit fullscreen mode

PEP 594 removed 21 legacy modules. pyrift flags all of them.


PyPy runtime differences

__del__ cleanup silently leaks on PyPy

class DatabaseConnection:
    def __del__(self):
        self.conn.close()  # may never run on PyPy
Enter fullscreen mode Exit fullscreen mode

CPython uses reference counting — __del__ runs immediately when the last reference drops. PyPy uses a tracing GC — __del__ may run much later or never. This silently leaks database connections, file handles, and locks.


sys.getrefcount() returns a dummy value on PyPy

if sys.getrefcount(obj) == 1:
    cleanup(obj)  # always wrong on PyPy
Enter fullscreen mode Exit fullscreen mode

PyPy has no reference counting. sys.getrefcount() always returns a dummy constant. Any logic based on it silently produces wrong results.


Why existing tools miss these

Tool What it catches What it misses
pylint / ruff Style, common bugs Runtime behaviour differences
mypy / pyright Type errors Runtime behaviour differences
pip-audit Known CVEs Behaviour differences
bandit Security patterns Behaviour differences
pyrift Silent runtime behaviour differences (that's the whole point)

pyrift does not replace any of these tools. It catches what they all miss.


Use it in CI

- name: Run pyrift
  run: |
    pip install pyrift
    pyrift scan .
Enter fullscreen mode Exit fullscreen mode

Exit code is 1 when errors are found — fails the build automatically.


Current state

  • 17 rules — 10 CPython version compatibility + 7 PyPy runtime differences
  • 70 tests — all passing on Python 3.10, 3.11, 3.12, 3.13
  • Zero dependencies — pure Python
  • MIT licensed

Try it

pip install pyrift
pyrift scan ./your_project
Enter fullscreen mode Exit fullscreen mode

GitHub: https://github.com/BHUVANSH855/pyrift

PyPI: https://pypi.org/project/pyrift/

Rule docs: https://github.com/BHUVANSH855/pyrift/blob/main/docs/rules.md

Contributions very welcome — especially new rules for behaviour differences you have personally encountered. Rule IDs CPY011+ and PPY008+ are open for community contributions.


Built by a CPython contributor and PyPy toolkit author - Bhuvansh Kataria

Top comments (0)