<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bhuvansh</title>
    <description>The latest articles on DEV Community by Bhuvansh (@bhuvansh855).</description>
    <link>https://dev.to/bhuvansh855</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4092226%2Ff4309a4b-4377-44d8-90e7-2e0258f321da.jpg</url>
      <title>DEV Community: Bhuvansh</title>
      <link>https://dev.to/bhuvansh855</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhuvansh855"/>
    <language>en</language>
    <item>
      <title>I built pyrift: a Python tool that catches runtime bugs linters can't see</title>
      <dc:creator>Bhuvansh</dc:creator>
      <pubDate>Mon, 24 Aug 2026 11:29:19 +0000</pubDate>
      <link>https://dev.to/bhuvansh855/i-built-pyrift-a-python-tool-that-catches-runtime-bugs-linters-cant-see-21ag</link>
      <guid>https://dev.to/bhuvansh855/i-built-pyrift-a-python-tool-that-catches-runtime-bugs-linters-cant-see-21ag</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;August 2026 update:&lt;/strong&gt; pyrift v0.5.0 is now available with 90 rules and 316 tests. &lt;code&gt;pip install --upgrade pyrift&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every Python developer has hit a bug that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passes all linting checks&lt;/li&gt;
&lt;li&gt;Passes all type checking&lt;/li&gt;
&lt;li&gt;Only appears at runtime, often in production&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I got frustrated with this pattern while working on CPython contributions and maintaining a PyPy review toolkit. So I built &lt;strong&gt;pyrift&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What pyrift does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyrift
pyrift scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It statically analyses your Python code and flags patterns that behave differently across CPython versions or between CPython and PyPy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ERROR] src/server.py:42 CPY002: Exception.add_note() requires Python 3.11+
→ Guard with: if sys.version_info &amp;gt;= (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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real examples of what it catches
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CPython version differences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Exception.add_note()&lt;/code&gt; — silent AttributeError on Python 3.10&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ConnectionError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_note&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check your network&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# AttributeError on 3.10
&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;p&gt;&lt;strong&gt;&lt;code&gt;asyncio.TaskGroup&lt;/code&gt; — AttributeError on Python 3.10&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TaskGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# AttributeError on 3.10
&lt;/span&gt;    &lt;span class="n"&gt;tg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TaskGroup&lt;/code&gt; was added in Python 3.11. This crashes silently on 3.10 with no hint of why.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;code&gt;datetime.utcnow()&lt;/code&gt; — deprecated since Python 3.12&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bad — deprecated, will be removed
&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Good — timezone-aware
&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns a naive datetime with no timezone info. Deprecated in 3.12, will be removed in a future version. Causes timezone bugs in logging and APIs.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;code&gt;asyncio.get_event_loop()&lt;/code&gt; — raises RuntimeError in Python 3.12+&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bad — RuntimeError in 3.12+ if no loop exists
&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Good
&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before 3.12 it silently created a new event loop. Now it raises &lt;code&gt;RuntimeError&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;21 stdlib modules removed in Python 3.13&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cgi&lt;/span&gt;        &lt;span class="c1"&gt;# ModuleNotFoundError on Python 3.13
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;telnetlib&lt;/span&gt;  &lt;span class="c1"&gt;# ModuleNotFoundError on Python 3.13
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynchat&lt;/span&gt;   &lt;span class="c1"&gt;# ModuleNotFoundError on Python 3.13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PEP 594 removed 21 legacy modules. pyrift flags all of them.&lt;/p&gt;




&lt;h3&gt;
  
  
  PyPy runtime differences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;__del__&lt;/code&gt; cleanup silently leaks on PyPy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseConnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__del__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# may never run on PyPy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CPython uses reference counting — &lt;code&gt;__del__&lt;/code&gt; runs immediately. PyPy uses a tracing GC — &lt;code&gt;__del__&lt;/code&gt; may run much later or never. This silently leaks database connections, file handles, and locks.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;code&gt;sys.getrefcount()&lt;/code&gt; returns a dummy value on PyPy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getrefcount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# always wrong on PyPy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PyPy has no reference counting. &lt;code&gt;sys.getrefcount()&lt;/code&gt; always returns a dummy constant. Any logic based on it silently produces wrong results.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;code&gt;sys.settrace()&lt;/code&gt; disables the JIT on PyPy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_tracer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# disables JIT — 10-100x slower on PyPy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debuggers and coverage tools that use &lt;code&gt;sys.settrace()&lt;/code&gt; will silently disable PyPy's JIT compiler, causing massive performance degradation.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;code&gt;sys.getsizeof()&lt;/code&gt; raises TypeError on PyPy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# TypeError on PyPy — always
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PyPy deliberately raises &lt;code&gt;TypeError&lt;/code&gt; because memory sizing by object is meaningless on its GC.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why existing tools miss these
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;What it misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pylint&lt;/code&gt; / &lt;code&gt;ruff&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Style, common bugs&lt;/td&gt;
&lt;td&gt;Runtime behaviour differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mypy&lt;/code&gt; / &lt;code&gt;pyright&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Type errors&lt;/td&gt;
&lt;td&gt;Runtime behaviour differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pip-audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Known CVEs&lt;/td&gt;
&lt;td&gt;Behaviour differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bandit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security patterns&lt;/td&gt;
&lt;td&gt;Behaviour differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pyrift&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Silent runtime behaviour differences&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(that's the whole point)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;pyrift does not replace any of these tools. It catches what they all miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use it in CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run pyrift&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;pip install pyrift&lt;/span&gt;
    &lt;span class="s"&gt;pyrift scan .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code is &lt;code&gt;1&lt;/code&gt; when errors are found — fails the build automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;90 rules&lt;/strong&gt; — 45 CPython version compatibility + 45 PyPy runtime differences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;316 tests&lt;/strong&gt; — all passing on Python 3.10, 3.11, 3.12, 3.13&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies&lt;/strong&gt; — pure Python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MIT licensed&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pyrift
pyrift scan ./your_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/BHUVANSH855/pyrift" rel="noopener noreferrer"&gt;https://github.com/BHUVANSH855/pyrift&lt;/a&gt;&lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/pyrift/" rel="noopener noreferrer"&gt;https://pypi.org/project/pyrift/&lt;/a&gt;&lt;br&gt;
Rule docs: &lt;a href="https://github.com/BHUVANSH855/pyrift/blob/main/docs/rules.md" rel="noopener noreferrer"&gt;https://github.com/BHUVANSH855/pyrift/blob/main/docs/rules.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contributions very welcome — especially new rules for behaviour differences you have personally encountered. Rule IDs &lt;code&gt;CPY046+&lt;/code&gt; and &lt;code&gt;PPY046+&lt;/code&gt; are open for community contributions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by a CPython contributor and PyPy toolkit author — Bhuvansh Kataria&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
