<?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>Forem: Tanay Baviskar</title>
    <description>The latest articles on Forem by Tanay Baviskar (@tanaybaviskar).</description>
    <link>https://forem.com/tanaybaviskar</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3914575%2F6c7a706e-1816-4a71-b3cf-aa80d020b120.jpeg</url>
      <title>Forem: Tanay Baviskar</title>
      <link>https://forem.com/tanaybaviskar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tanaybaviskar"/>
    <language>en</language>
    <item>
      <title>I Built a Tool That Watches Your Python App Run and Tells You What Static Analysis Can't</title>
      <dc:creator>Tanay Baviskar</dc:creator>
      <pubDate>Tue, 05 May 2026 19:07:12 +0000</pubDate>
      <link>https://forem.com/tanaybaviskar/i-built-a-tool-that-watches-your-python-app-run-and-tells-you-what-static-analysis-cant-45g2</link>
      <guid>https://forem.com/tanaybaviskar/i-built-a-tool-that-watches-your-python-app-run-and-tells-you-what-static-analysis-cant-45g2</guid>
      <description>&lt;p&gt;For the past few months I kept running into the same problem.&lt;/p&gt;

&lt;p&gt;Mypy says the code is fine. Pylance says the code is fine. The tests pass. Then production breaks because some legacy caller is passing a &lt;code&gt;float&lt;/code&gt; to a function annotated as &lt;code&gt;int&lt;/code&gt;, and it's been doing it for six months without anyone noticing.&lt;/p&gt;

&lt;p&gt;Static analysis can only see what's written. It can't see what's &lt;em&gt;called&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Ghost&lt;/strong&gt;, a Python runtime observer that hooks into &lt;code&gt;sys.setprofile&lt;/code&gt; and watches your app run, then tells you what actually happened.&lt;br&gt;
&lt;/p&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;ghost-observer
ghost run app.py
ghost report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No code changes. No decorators. No configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually shows you
&lt;/h2&gt;

&lt;p&gt;Here's a real example. I have a function:&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;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mypy is happy. But after running under Ghost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ghost report --sort exceptions

FUNCTION                    CALLS   EXC%   MEAN LAT   DOM. ARG SIG
calculate_discount:12           8     0%      &amp;lt;1µs     (int, int)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ghost anomalies

[MEDIUM] type_mismatch   calculate_discount (line 12)
         param 'price' annotated as int but observed 'float' in 3/8 calls (38%)
         param 'quantity' annotated as int but observed 'float' in 2/8 calls (25%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;38% of real calls are passing floats. Mypy never saw it. Ghost caught it on the first run.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four things Ghost detects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Type mismatches&lt;/strong&gt;&lt;br&gt;
Compares PEP-484 annotations against observed runtime types. Every call. Not just the ones in your test suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Never-called functions&lt;/strong&gt;&lt;br&gt;
Ground-truth dead code detection. Not "this function isn't reachable according to the call graph", literally "this function was never called while the app ran." Real traffic, real answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. High exception rates&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HIGH] high_exc_rate   validate_order (line 34)
       exception rate 60.0% (18/30 calls) exceeds threshold 5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Silent failures at 60% call rate. No logs, no alerts, just Ghost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Latency outliers&lt;/strong&gt;&lt;br&gt;
Flags functions whose mean latency is &amp;gt;2.5σ above their module average. Finds the one slow function hiding among ten fast ones.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpflwdxa7tmrhey7eyffu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpflwdxa7tmrhey7eyffu.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Ghost installs two hooks before your app starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sys.setprofile&lt;/code&gt; — captures every call and return (~50ns overhead per event)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sys.settrace&lt;/code&gt; — exception detection only (distinguishes &lt;code&gt;return None&lt;/code&gt; from &lt;code&gt;raise&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events flow into an in-memory buffer → background thread flushes to SQLite every 1–5s (adaptive) → aggregator builds per-function profiles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy:&lt;/strong&gt; Ghost captures &lt;code&gt;type(value).__qualname__&lt;/code&gt;, never the value itself. No secrets, PII, or passwords ever enter the buffer.&lt;/p&gt;


&lt;h2&gt;
  
  
  All the commands
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Observe any script&lt;/span&gt;
ghost run app.py
ghost run manage.py runserver  &lt;span class="c"&gt;# Django&lt;/span&gt;
ghost run &lt;span class="nt"&gt;-m&lt;/span&gt; uvicorn main:app  &lt;span class="c"&gt;# FastAPI&lt;/span&gt;

&lt;span class="c"&gt;# Profile table&lt;/span&gt;
ghost report
ghost report &lt;span class="nt"&gt;--sort&lt;/span&gt; latency
ghost report &lt;span class="nt"&gt;--sort&lt;/span&gt; exceptions

&lt;span class="c"&gt;# Deep dive on one function&lt;/span&gt;
ghost explain process_order
ghost explain validate_user &lt;span class="nt"&gt;--backend&lt;/span&gt; gemini  &lt;span class="c"&gt;# AI analysis with GEMINI_API_KEY&lt;/span&gt;

&lt;span class="c"&gt;# Anomaly detection&lt;/span&gt;
ghost anomalies
ghost anomalies &lt;span class="nt"&gt;--exc-threshold&lt;/span&gt; 0.02   &lt;span class="c"&gt;# flag anything above 2%&lt;/span&gt;

&lt;span class="c"&gt;# Compare two runs&lt;/span&gt;
ghost sessions
ghost diff &amp;lt;session-1&amp;gt; &amp;lt;session-2&amp;gt;

&lt;span class="c"&gt;# Live-updating terminal dashboard&lt;/span&gt;
ghost watch
ghost watch &lt;span class="nt"&gt;--interval&lt;/span&gt; 1 &lt;span class="nt"&gt;--sort&lt;/span&gt; latency

&lt;span class="c"&gt;# Export for other tools&lt;/span&gt;
ghost &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;-o&lt;/span&gt; profile.json
ghost &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; csv &lt;span class="nt"&gt;-o&lt;/span&gt; profile.csv

&lt;span class="c"&gt;# Housekeeping&lt;/span&gt;
ghost clean &lt;span class="nt"&gt;--older-than&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparing two sessions
&lt;/h2&gt;

&lt;p&gt;This is where Ghost gets genuinely useful for refactoring. Run before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ghost run app.py
&lt;span class="c"&gt;# make your changes&lt;/span&gt;
ghost run app.py
ghost diff &amp;lt;session-before&amp;gt; &amp;lt;session-after&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;Ghost diff  session-abc123  →  session-def456
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  ── changed (3) ──
  CHANGED   process_order (line 42)
             mean latency: 12.50ms → 4.20ms  (0.34×) ↓ faster
             call count: 100 → 100  (no change)
&lt;span class="err"&gt;
&lt;/span&gt;  CHANGED   validate_order (line 18)
             exception rate: 60.0% → 8.0%  ↓
&lt;span class="err"&gt;
&lt;/span&gt;  CHANGED   get_product_details (line 87)
             mean latency: 45.00ms → 2.10ms  (0.05×) ↓ faster
             new arg signature observed: (int, str)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traffic-weighted proof that your refactor actually helped.&lt;/p&gt;




&lt;h2&gt;
  
  
  The competitive gap
&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 sees&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;mypy / Pylance&lt;/td&gt;
&lt;td&gt;Source code types (annotations only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cProfile&lt;/td&gt;
&lt;td&gt;Call counts and timing (no types, no exceptions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;Errors that reach your error handler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ghost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every call, actual types, real exception rates, measured latency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The four things Ghost detects — runtime type mismatches, truly dead code, silent exception rates, latency outliers — are &lt;strong&gt;invisible to static tools by definition&lt;/strong&gt;. They only exist at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install
&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;ghost-observer

&lt;span class="c"&gt;# Optional: AI-powered explanations&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;ghost-observer[gemini]
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-key
ghost explain your_slow_function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/Tanaybaviskar/ghost-observer" rel="noopener noreferrer"&gt;github.com/Tanaybaviskar/ghost-observer&lt;/a&gt;&lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/ghost-observer/" rel="noopener noreferrer"&gt;pypi.org/project/ghost-observer&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Would love to hear what anomalies Ghost finds in your codebase. Drop them in the comments.&lt;/p&gt;




</description>
      <category>python</category>
      <category>django</category>
      <category>performance</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
